diff --git a/cli/ingress-controller/flags.go b/cli/ingress-controller/flags.go index 6836cf3e3e..223baf6e52 100644 --- a/cli/ingress-controller/flags.go +++ b/cli/ingress-controller/flags.go @@ -58,10 +58,11 @@ type cliConfig struct { KongCustomEntitiesSecret string // Resource filtering - WatchNamespace string - SkipClasslessIngressV1beta1 bool - IngressClass string - ElectionID string + WatchNamespace string + ProcessClasslessIngressV1beta1 bool + ProcessClasslessKongConsumer bool + IngressClass string + ElectionID string // Ingress Status publish resource PublishService string @@ -325,7 +326,8 @@ func parseFlags() (cliConfig, error) { // Resource filtering config.WatchNamespace = viper.GetString("watch-namespace") - config.SkipClasslessIngressV1beta1 = viper.GetBool("skip-classless-ingress-v1beta1") + config.ProcessClasslessIngressV1beta1 = viper.GetBool("process-classless-ingress-v1beta1") + config.ProcessClasslessKongConsumer = viper.GetBool("process-classless-kong-consumer") config.IngressClass = viper.GetString("ingress-class") config.ElectionID = viper.GetString("election-id") diff --git a/cli/ingress-controller/main.go b/cli/ingress-controller/main.go index bb8ea8a11a..4cc3c548ec 100644 --- a/cli/ingress-controller/main.go +++ b/cli/ingress-controller/main.go @@ -38,7 +38,6 @@ import ( "github.com/hashicorp/go-uuid" "github.com/kong/go-kong/kong" "github.com/kong/kubernetes-ingress-controller/internal/admission" - "github.com/kong/kubernetes-ingress-controller/internal/ingress/annotations" "github.com/kong/kubernetes-ingress-controller/internal/ingress/controller" "github.com/kong/kubernetes-ingress-controller/internal/ingress/store" "github.com/kong/kubernetes-ingress-controller/internal/ingress/utils" @@ -350,10 +349,8 @@ func main() { updateChannel := channels.NewRingChannel(1024) reh := controller.ResourceEventHandler{ UpdateCh: updateChannel, - IsValidIngresClass: annotations.IngressClassValidatorFunc( - cliConfig.IngressClass, - cliConfig.SkipClasslessIngressV1beta1), } + var informers []cache.SharedIndexInformer var cacheStores store.CacheStores @@ -431,9 +428,10 @@ func main() { runtime.HandleError(fmt.Errorf("Timed out waiting for caches to sync")) } - store := store.New(cacheStores, cliConfig.IngressClass, cliConfig.SkipClasslessIngressV1beta1, - log.WithField("component", "store")) - kong, err := controller.NewKongController(ctx, &controllerConfig, updateChannel, store) + store := store.New(cacheStores, cliConfig.IngressClass, cliConfig.ProcessClasslessIngressV1beta1, + cliConfig.ProcessClasslessKongConsumer, log.WithField("component", "store")) + kong, err := controller.NewKongController(ctx, &controllerConfig, updateChannel, + store) if err != nil { log.Fatalf("failed to create a controller: %v", err) } diff --git a/cli/ingress-controller/main_test.go b/cli/ingress-controller/main_test.go index acea21633d..58b780b11c 100644 --- a/cli/ingress-controller/main_test.go +++ b/cli/ingress-controller/main_test.go @@ -80,7 +80,8 @@ func TestHandleSigterm(t *testing.T) { KubeClient: kubeClient, }, channels.NewRingChannel(1024), - store.New(store.CacheStores{}, conf.IngressClass, conf.SkipClasslessIngressV1beta1, logrus.New()), + store.New(store.CacheStores{}, conf.IngressClass, conf.ProcessClasslessIngressV1beta1, + conf.ProcessClasslessKongConsumer, logrus.New()), ) exitCh := make(chan int, 1) diff --git a/internal/ingress/annotations/annotations.go b/internal/ingress/annotations/annotations.go index fcad252598..727e9f68c9 100644 --- a/internal/ingress/annotations/annotations.go +++ b/internal/ingress/annotations/annotations.go @@ -22,6 +22,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +type ClassMatching int + +const ( + IgnoreClassMatch ClassMatching = iota + ExactOrEmptyClassMatch ClassMatching = iota + ExactClassMatch ClassMatching = iota +) + const ( ingressClassKey = "kubernetes.io/ingress.class" @@ -49,39 +57,42 @@ const ( DefaultIngressClass = "kong" ) -func validIngress(ingressAnnotationValue, ingressClass string, skipClasslessIngress bool) bool { - // we have 2 valid combinations - // 1 - ingress with default class | blank annotation on ingress - // 2 - ingress with specific class | same annotation on ingress - // - // and 2 invalid combinations - // 3 - ingress with default class | fixed annotation on ingress - // 4 - ingress with specific class | different annotation on ingress - if ingressAnnotationValue == "" && !skipClasslessIngress && ingressClass == DefaultIngressClass { +func validIngress(ingressAnnotationValue, ingressClass string, handling ClassMatching) bool { + switch handling { + case IgnoreClassMatch: + // class is not considered at all. any value, even a mismatch, is valid return true + case ExactOrEmptyClassMatch: + // aka lazy. exact match desired, but empty permitted + return ingressAnnotationValue == "" || ingressAnnotationValue == ingressClass + case ExactClassMatch: + // what it says on the tin + // this may be another place we want to return a warning, since an empty-class resource will never be valid + return ingressAnnotationValue == ingressClass + default: + panic("invalid ingress class handling option received") } - return ingressAnnotationValue == ingressClass } // IngressClassValidatorFunc returns a function which can validate if an Object // belongs to an the ingressClass or not. func IngressClassValidatorFunc( - ingressClass string, skipClasslessIngress bool) func(obj metav1.Object) bool { + ingressClass string) func(obj metav1.Object, handling ClassMatching) bool { - return func(obj metav1.Object) bool { + return func(obj metav1.Object, handling ClassMatching) bool { ingress := obj.GetAnnotations()[ingressClassKey] - return validIngress(ingress, ingressClass, skipClasslessIngress) + return validIngress(ingress, ingressClass, handling) } } // IngressClassValidatorFuncFromObjectMeta returns a function which // can validate if an ObjectMeta belongs to an the ingressClass or not. func IngressClassValidatorFuncFromObjectMeta( - ingressClass string, skipClasslessIngress bool) func(obj *metav1.ObjectMeta) bool { + ingressClass string) func(obj *metav1.ObjectMeta, handling ClassMatching) bool { - return func(obj *metav1.ObjectMeta) bool { + return func(obj *metav1.ObjectMeta, handling ClassMatching) bool { ingress := obj.GetAnnotations()[ingressClassKey] - return validIngress(ingress, ingressClass, skipClasslessIngress) + return validIngress(ingress, ingressClass, handling) } } diff --git a/internal/ingress/annotations/annotations_test.go b/internal/ingress/annotations/annotations_test.go index f784b6fa47..205dd62928 100644 --- a/internal/ingress/annotations/annotations_test.go +++ b/internal/ingress/annotations/annotations_test.go @@ -27,21 +27,27 @@ import ( func TestIngressClassValidatorFunc(t *testing.T) { tests := []struct { - ingress string - skipClasslessIngress bool - controller string - isValid bool + ingress string // the class set on the Ingress resource + classMatching ClassMatching // the "user" classless ingress flag value, translated to its match strategy + controller string // the class set on the controller + isValid bool // the expected verdict }{ - {"", false, "", true}, - {"", false, "kong", true}, - {"", true, "kong", false}, - {"kong", false, "kong", true}, - {"kong", true, "kong", true}, - {"custom", false, "custom", true}, - {"", false, "killer", false}, - {"custom", false, "kong", false}, - {"custom", true, "kong", false}, - {"", true, "custom", false}, + {"", ExactOrEmptyClassMatch, "", true}, + {"", ExactOrEmptyClassMatch, DefaultIngressClass, true}, + {"", ExactClassMatch, DefaultIngressClass, false}, + {DefaultIngressClass, ExactOrEmptyClassMatch, DefaultIngressClass, true}, + {DefaultIngressClass, ExactClassMatch, DefaultIngressClass, true}, + {"custom", ExactOrEmptyClassMatch, "custom", true}, + {"", ExactOrEmptyClassMatch, "killer", true}, + {"custom", ExactOrEmptyClassMatch, DefaultIngressClass, false}, + {"custom", ExactClassMatch, DefaultIngressClass, false}, + {"", ExactOrEmptyClassMatch, "custom", true}, + {"", ExactClassMatch, "kozel", false}, + {"kozel", ExactOrEmptyClassMatch, "kozel", true}, + {"kozel", ExactClassMatch, "kozel", true}, + {"", ExactOrEmptyClassMatch, "killer", true}, + {"custom", ExactOrEmptyClassMatch, "kozel", false}, + {"custom", ExactClassMatch, "kozel", false}, } ing := &extensions.Ingress{ @@ -55,10 +61,11 @@ func TestIngressClassValidatorFunc(t *testing.T) { ing.SetAnnotations(data) for _, test := range tests { ing.Annotations[ingressClassKey] = test.ingress - f := IngressClassValidatorFunc(test.controller, test.skipClasslessIngress) - b := f(&ing.ObjectMeta) - if b != test.isValid { - t.Errorf("test %v - expected %v but %v was returned", test, test.isValid, b) + f := IngressClassValidatorFunc(test.controller) + + result := f(&ing.ObjectMeta, test.classMatching) + if result != test.isValid { + t.Errorf("test %v - expected %v but %v was returned", test, test.isValid, result) } } } diff --git a/internal/ingress/controller/event_handler.go b/internal/ingress/controller/event_handler.go index 1abe5b9bd0..62eed21235 100644 --- a/internal/ingress/controller/event_handler.go +++ b/internal/ingress/controller/event_handler.go @@ -5,15 +5,12 @@ import ( "github.com/eapache/channels" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ResourceEventHandler is "ingress.class" aware resource // handler. type ResourceEventHandler struct { - IsValidIngresClass func(object metav1.Object) bool - UpdateCh *channels.RingChannel + UpdateCh *channels.RingChannel } // EventType type of event associated with an informer @@ -37,15 +34,7 @@ type Event struct { Old interface{} } -// OnAdd is invoked whenever a resource is added. func (reh ResourceEventHandler) OnAdd(obj interface{}) { - object, err := meta.Accessor(obj) - if err != nil { - return - } - if !reh.IsValidIngresClass(object) { - return - } reh.UpdateCh.In() <- Event{ Type: CreateEvent, Obj: obj, @@ -54,14 +43,6 @@ func (reh ResourceEventHandler) OnAdd(obj interface{}) { // OnDelete is invoked whenever a resource is deleted. func (reh ResourceEventHandler) OnDelete(obj interface{}) { - object, err := meta.Accessor(obj) - if err != nil { - return - } - if !reh.IsValidIngresClass(object) { - return - } - reh.UpdateCh.In() <- Event{ Type: DeleteEvent, Obj: obj, @@ -71,21 +52,6 @@ func (reh ResourceEventHandler) OnDelete(obj interface{}) { // OnUpdate is invoked whenever a resource is changed. old holds // the previous resource and cur is the updated resource. func (reh ResourceEventHandler) OnUpdate(old, cur interface{}) { - oldObj, err := meta.Accessor(old) - if err != nil { - return - } - curObj, err := meta.Accessor(cur) - if err != nil { - return - } - validOld := reh.IsValidIngresClass(oldObj) - validCur := reh.IsValidIngresClass(curObj) - - if !validCur && !validOld { - return - } - reh.UpdateCh.In() <- Event{ Type: UpdateEvent, Obj: cur, diff --git a/internal/ingress/controller/parser/parser_test.go b/internal/ingress/controller/parser/parser_test.go index d0fb8750d0..87bae3eb68 100644 --- a/internal/ingress/controller/parser/parser_test.go +++ b/internal/ingress/controller/parser/parser_test.go @@ -148,6 +148,9 @@ func TestGlobalPlugin(t *testing.T) { Labels: map[string]string{ "global": "true", }, + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Protocols: []string{"http"}, PluginName: "basic-auth", @@ -194,7 +197,8 @@ func TestSecretConfigurationPlugin(t *testing.T) { Name: "foo", Namespace: "default", Annotations: map[string]string{ - "plugins.konghq.com": "foo-plugin", + "plugins.konghq.com": "foo-plugin", + "kubernetes.io/ingress.class": "kong", }, }, Spec: networking.IngressSpec{ @@ -223,7 +227,8 @@ func TestSecretConfigurationPlugin(t *testing.T) { Name: "bar", Namespace: "default", Annotations: map[string]string{ - "plugins.konghq.com": "bar-plugin", + "plugins.konghq.com": "bar-plugin", + "kubernetes.io/ingress.class": "kong", }, }, Spec: networking.IngressSpec{ @@ -273,6 +278,29 @@ func TestSecretConfigurationPlugin(t *testing.T) { Labels: map[string]string{ "global": "true", }, + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, + }, + Protocols: []string{"http"}, + PluginName: "basic-auth", + ConfigFrom: configurationv1.NamespacedConfigSource{ + SecretValue: configurationv1.NamespacedSecretValueFromSource{ + Key: "basic-auth-config", + Secret: "conf-secret", + Namespace: "default", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "global-broken-bar-plugin", + Labels: map[string]string{ + "global": "true", + }, + Annotations: map[string]string{ + // explicitly none, this should not get rendered + }, }, Protocols: []string{"http"}, PluginName: "basic-auth", @@ -816,6 +844,9 @@ func TestServiceClientCertificate(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ Rules: []networking.IngressRule{ @@ -895,6 +926,9 @@ func TestServiceClientCertificate(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ Rules: []networking.IngressRule{ @@ -963,6 +997,7 @@ func TestKongRouteAnnotations(t *testing.T) { Namespace: "default", Annotations: map[string]string{ "configuration.konghq.com/strip-path": "trUe", + "kubernetes.io/ingress.class": "kong", }, }, Spec: networking.IngressSpec{ @@ -1039,6 +1074,7 @@ func TestKongRouteAnnotations(t *testing.T) { Name: "bar", Namespace: "default", Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", "configuration.konghq.com/strip-path": "false", }, }, @@ -1117,6 +1153,7 @@ func TestKongRouteAnnotations(t *testing.T) { Name: "bar", Namespace: "default", Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", "konghq.com/https-redirect-status-code": "301", }, }, @@ -1196,6 +1233,7 @@ func TestKongRouteAnnotations(t *testing.T) { Name: "bar", Namespace: "default", Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", "konghq.com/https-redirect-status-code": "whoops", }, }, @@ -1274,7 +1312,8 @@ func TestKongRouteAnnotations(t *testing.T) { Name: "bar", Namespace: "default", Annotations: map[string]string{ - "konghq.com/preserve-host": "faLsE", + "konghq.com/preserve-host": "faLsE", + "kubernetes.io/ingress.class": "kong", }, }, Spec: networking.IngressSpec{ @@ -1352,7 +1391,8 @@ func TestKongRouteAnnotations(t *testing.T) { Name: "bar", Namespace: "default", Annotations: map[string]string{ - "konghq.com/preserve-host": "wiggle wiggle wiggle", + "kubernetes.io/ingress.class": "kong", + "konghq.com/preserve-host": "wiggle wiggle wiggle", }, }, Spec: networking.IngressSpec{ @@ -1430,7 +1470,8 @@ func TestKongRouteAnnotations(t *testing.T) { Name: "bar", Namespace: "default", Annotations: map[string]string{ - "konghq.com/regex-priority": "10", + "konghq.com/regex-priority": "10", + "kubernetes.io/ingress.class": "kong", }, }, Spec: networking.IngressSpec{ @@ -1508,7 +1549,8 @@ func TestKongRouteAnnotations(t *testing.T) { Name: "bar", Namespace: "default", Annotations: map[string]string{ - "konghq.com/regex-priority": "IAmAString", + "konghq.com/regex-priority": "IAmAString", + "kubernetes.io/ingress.class": "kong", }, }, Spec: networking.IngressSpec{ @@ -1580,15 +1622,17 @@ func TestKongRouteAnnotations(t *testing.T) { }) } -func TestKongSkipClasslessIngress(t *testing.T) { +func TestKongProcessClasslessIngress(t *testing.T) { assert := assert.New(t) t.Run("Kong classless ingress evaluated (true)", func(t *testing.T) { ingresses := []*networking.Ingress{ { ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - Namespace: "default", - Annotations: map[string]string{}, + Name: "bar", + Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ Rules: []networking.IngressRule{ @@ -1622,9 +1666,8 @@ func TestKongSkipClasslessIngress(t *testing.T) { } store, err := store.NewFakeStore(store.FakeObjects{ - Ingresses: ingresses, - Services: services, - SkipClasslessIngress: false, + Ingresses: ingresses, + Services: services, }) assert.Nil(err) parser := New(store, logrus.New()) @@ -1639,9 +1682,8 @@ func TestKongSkipClasslessIngress(t *testing.T) { ingresses := []*networking.Ingress{ { ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - Namespace: "default", - Annotations: map[string]string{}, + Name: "bar", + Namespace: "default", }, Spec: networking.IngressSpec{ Rules: []networking.IngressRule{ @@ -1675,9 +1717,8 @@ func TestKongSkipClasslessIngress(t *testing.T) { } store, err := store.NewFakeStore(store.FakeObjects{ - Ingresses: ingresses, - Services: services, - SkipClasslessIngress: true, + Ingresses: ingresses, + Services: services, }) assert.Nil(err) parser := New(store, logrus.New()) @@ -1825,6 +1866,9 @@ func TestKongServiceAnnotations(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ Rules: []networking.IngressRule{ @@ -1903,6 +1947,9 @@ func TestKongServiceAnnotations(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ Rules: []networking.IngressRule{ @@ -1990,7 +2037,8 @@ func TestKongServiceAnnotations(t *testing.T) { Name: "bar", Namespace: "default", Annotations: map[string]string{ - "konghq.com/methods": "POST,GET", + "konghq.com/methods": "POST,GET", + "kubernetes.io/ingress.class": "kong", }, }, Spec: networking.IngressSpec{ @@ -2071,6 +2119,9 @@ func TestDefaultBackend(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "ing-with-default-backend", Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ Backend: &networking.IngressBackend{ @@ -2114,6 +2165,9 @@ func TestDefaultBackend(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ Rules: []networking.IngressRule{ @@ -2180,6 +2234,9 @@ func TestParserSecret(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ TLS: []networking.IngressTLS{ @@ -2236,6 +2293,9 @@ func TestParserSecret(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ TLS: []networking.IngressTLS{ @@ -2250,6 +2310,9 @@ func TestParserSecret(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "bar", Namespace: "ns1", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ TLS: []networking.IngressTLS{ @@ -2325,6 +2388,9 @@ func TestParserSecret(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ TLS: []networking.IngressTLS{ @@ -2405,7 +2471,8 @@ func TestPluginAnnotations(t *testing.T) { Name: "foo", Namespace: "default", Annotations: map[string]string{ - "plugins.konghq.com": "foo-plugin", + "plugins.konghq.com": "foo-plugin", + "kubernetes.io/ingress.class": "kong", }, }, Spec: networking.IngressSpec{ @@ -2494,7 +2561,8 @@ func TestPluginAnnotations(t *testing.T) { Name: "foo", Namespace: "default", Annotations: map[string]string{ - "plugins.konghq.com": "foo-plugin", + "plugins.konghq.com": "foo-plugin", + "kubernetes.io/ingress.class": "kong", }, }, Spec: networking.IngressSpec{ @@ -2578,7 +2646,8 @@ func TestPluginAnnotations(t *testing.T) { Name: "foo", Namespace: "default", Annotations: map[string]string{ - "plugins.konghq.com": "foo-plugin", + "plugins.konghq.com": "foo-plugin", + "kubernetes.io/ingress.class": "kong", }, }, Spec: networking.IngressSpec{ @@ -2639,7 +2708,8 @@ func TestPluginAnnotations(t *testing.T) { Name: "foo", Namespace: "default", Annotations: map[string]string{ - "plugins.konghq.com": "does-not-exist", + "plugins.konghq.com": "does-not-exist", + "kubernetes.io/ingress.class": "kong", }, }, Spec: networking.IngressSpec{ @@ -2689,6 +2759,9 @@ func TestParseIngressRules(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "foo-namespace", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ Rules: []networking.IngressRule{ diff --git a/internal/ingress/store/fake_store.go b/internal/ingress/store/fake_store.go index 551fe01702..571a0f99b1 100644 --- a/internal/ingress/store/fake_store.go +++ b/internal/ingress/store/fake_store.go @@ -26,17 +26,16 @@ func clusterResourceKeyFunc(obj interface{}) (string, error) { // FakeObjects can be used to populate a fake Store. type FakeObjects struct { - Ingresses []*networking.Ingress - TCPIngresses []*configurationv1beta1.TCPIngress - Services []*apiv1.Service - Endpoints []*apiv1.Endpoints - Secrets []*apiv1.Secret - KongPlugins []*configurationv1.KongPlugin - KongClusterPlugins []*configurationv1.KongClusterPlugin - KongIngresses []*configurationv1.KongIngress - KongConsumers []*configurationv1.KongConsumer - KongCredentials []*configurationv1.KongCredential - SkipClasslessIngress bool + Ingresses []*networking.Ingress + TCPIngresses []*configurationv1beta1.TCPIngress + Services []*apiv1.Service + Endpoints []*apiv1.Endpoints + Secrets []*apiv1.Secret + KongPlugins []*configurationv1.KongPlugin + KongClusterPlugins []*configurationv1.KongClusterPlugin + KongIngresses []*configurationv1.KongIngress + KongConsumers []*configurationv1.KongConsumer + KongCredentials []*configurationv1.KongCredential KnativeIngresses []*knative.Ingress } @@ -124,7 +123,6 @@ func NewFakeStore( return nil, err } } - s = Store{ stores: CacheStores{ Ingress: ingressStore, @@ -141,7 +139,9 @@ func NewFakeStore( KnativeIngress: knativeIngressStore, }, - isValidIngresClass: annotations.IngressClassValidatorFuncFromObjectMeta("kong", objects.SkipClasslessIngress), + isValidIngressClass: annotations.IngressClassValidatorFuncFromObjectMeta("kong"), + ingressClassMatching: annotations.ExactClassMatch, + kongConsumerClassMatching: annotations.ExactClassMatch, } return s, nil } diff --git a/internal/ingress/store/fake_store_test.go b/internal/ingress/store/fake_store_test.go index afc95acb36..9991909090 100644 --- a/internal/ingress/store/fake_store_test.go +++ b/internal/ingress/store/fake_store_test.go @@ -94,6 +94,9 @@ func TestFakeStoreIngress(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, Spec: networking.IngressSpec{ Rules: []networking.IngressRule{ @@ -160,6 +163,27 @@ func TestFakeStoreListTCPIngress(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, + }, + Spec: configurationv1beta1.IngressSpec{ + Rules: []configurationv1beta1.IngressRule{ + { + Port: 9000, + Backend: configurationv1beta1.IngressBackend{ + ServiceName: "foo-svc", + ServicePort: 80, + }, + }, + }, + }, + }, + { + // this TCPIngress should *not* be loaded, as it lacks a class + ObjectMeta: metav1.ObjectMeta{ + Name: "baz", + Namespace: "default", }, Spec: configurationv1beta1.IngressSpec{ Rules: []configurationv1beta1.IngressRule{ @@ -299,6 +323,9 @@ func TestFakeStoreConsumer(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "default", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, }, } @@ -374,9 +401,13 @@ func TestFakeStoreClusterPlugins(t *testing.T) { Labels: map[string]string{ "global": "true", }, + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + }, }, }, { + // invalid due to lack of class, not loaded ObjectMeta: metav1.ObjectMeta{ Name: "bar", Labels: map[string]string{ @@ -394,7 +425,7 @@ func TestFakeStoreClusterPlugins(t *testing.T) { assert.Nil(err) assert.NotNil(store) plugins, err = store.ListGlobalKongClusterPlugins() - assert.Len(plugins, 2) + assert.Len(plugins, 1) plugin, err := store.GetKongClusterPlugin("foo") assert.NotNil(plugin) diff --git a/internal/ingress/store/store.go b/internal/ingress/store/store.go index 7cb054b5bb..1cac583fd6 100644 --- a/internal/ingress/store/store.go +++ b/internal/ingress/store/store.go @@ -83,9 +83,10 @@ type Store struct { ingressClass string - skipClasslessIngress bool + ingressClassMatching annotations.ClassMatching + kongConsumerClassMatching annotations.ClassMatching - isValidIngresClass func(objectMeta *metav1.ObjectMeta) bool + isValidIngressClass func(objectMeta *metav1.ObjectMeta, handling annotations.ClassMatching) bool logger logrus.FieldLogger } @@ -109,13 +110,27 @@ type CacheStores struct { } // New creates a new object store to be used in the ingress controller -func New(cs CacheStores, ingressClass string, skipClasslessIngress bool, logger logrus.FieldLogger) Storer { +func New(cs CacheStores, ingressClass string, processClasslessIngress bool, processClasslessKongConsumer bool, + logger logrus.FieldLogger) Storer { + var ingressClassMatching annotations.ClassMatching + var kongConsumerClassMatching annotations.ClassMatching + if processClasslessIngress { + ingressClassMatching = annotations.ExactOrEmptyClassMatch + } else { + ingressClassMatching = annotations.ExactClassMatch + } + if processClasslessKongConsumer { + kongConsumerClassMatching = annotations.ExactOrEmptyClassMatch + } else { + kongConsumerClassMatching = annotations.ExactClassMatch + } return Store{ - stores: cs, - ingressClass: ingressClass, - skipClasslessIngress: skipClasslessIngress, - isValidIngresClass: annotations.IngressClassValidatorFuncFromObjectMeta(ingressClass, skipClasslessIngress), - logger: logger, + stores: cs, + ingressClass: ingressClass, + ingressClassMatching: ingressClassMatching, + kongConsumerClassMatching: kongConsumerClassMatching, + isValidIngressClass: annotations.IngressClassValidatorFuncFromObjectMeta(ingressClass), + logger: logger, } } @@ -151,7 +166,7 @@ func (s Store) ListIngresses() []*networkingv1beta1.Ingress { var ingresses []*networkingv1beta1.Ingress for _, item := range s.stores.Ingress.List() { ing := s.networkingIngressV1Beta1(item) - if !s.isValidIngresClass(&ing.ObjectMeta) { + if !s.isValidIngressClass(&ing.ObjectMeta, s.ingressClassMatching) { continue } ingresses = append(ingresses, ing) @@ -167,7 +182,7 @@ func (s Store) ListTCPIngresses() ([]*configurationv1beta1.TCPIngress, error) { err := cache.ListAll(s.stores.TCPIngress, labels.NewSelector(), func(ob interface{}) { ing, ok := ob.(*configurationv1beta1.TCPIngress) - if ok && s.isValidIngresClass(&ing.ObjectMeta) { + if ok && s.isValidIngressClass(&ing.ObjectMeta, annotations.ExactClassMatch) { ingresses = append(ingresses, ing) } }) @@ -277,7 +292,7 @@ func (s Store) ListKongConsumers() []*configurationv1.KongConsumer { var consumers []*configurationv1.KongConsumer for _, item := range s.stores.Consumer.List() { c, ok := item.(*configurationv1.KongConsumer) - if ok && s.isValidIngresClass(&c.ObjectMeta) { + if ok && s.isValidIngressClass(&c.ObjectMeta, s.kongConsumerClassMatching) { consumers = append(consumers, c) } } @@ -291,7 +306,7 @@ func (s Store) ListKongCredentials() []*configurationv1.KongCredential { var credentials []*configurationv1.KongCredential for _, item := range s.stores.Credential.List() { c, ok := item.(*configurationv1.KongCredential) - if ok && s.isValidIngresClass(&c.ObjectMeta) { + if ok && s.isValidIngressClass(&c.ObjectMeta, annotations.IgnoreClassMatch) { credentials = append(credentials, c) } } @@ -316,7 +331,7 @@ func (s Store) ListGlobalKongPlugins() ([]*configurationv1.KongPlugin, error) { labels.NewSelector().Add(*req), func(ob interface{}) { p, ok := ob.(*configurationv1.KongPlugin) - if ok && s.isValidIngresClass(&p.ObjectMeta) { + if ok && s.isValidIngressClass(&p.ObjectMeta, s.ingressClassMatching) { plugins = append(plugins, p) } }) @@ -340,7 +355,7 @@ func (s Store) ListGlobalKongClusterPlugins() ([]*configurationv1.KongClusterPlu labels.NewSelector().Add(*req), func(ob interface{}) { p, ok := ob.(*configurationv1.KongClusterPlugin) - if ok && s.isValidIngresClass(&p.ObjectMeta) { + if ok && s.isValidIngressClass(&p.ObjectMeta, annotations.ExactClassMatch) { plugins = append(plugins, p) } })