diff --git a/pkg/provider/kubernetes/ingress/client.go b/pkg/provider/kubernetes/ingress/client.go index f4af6c377..84d6bb835 100644 --- a/pkg/provider/kubernetes/ingress/client.go +++ b/pkg/provider/kubernetes/ingress/client.go @@ -5,11 +5,11 @@ import ( "errors" "fmt" "io/ioutil" - "strconv" "time" "github.com/containous/traefik/v2/pkg/log" "github.com/golang/protobuf/proto" + "github.com/hashicorp/go-version" corev1 "k8s.io/api/core/v1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1beta1 "k8s.io/api/networking/v1beta1" @@ -55,7 +55,7 @@ type Client interface { GetSecret(namespace, name string) (*corev1.Secret, bool, error) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) UpdateIngressStatus(ing *networkingv1beta1.Ingress, ip, hostname string) error - GetServerVersion() (major, minor int, err error) + GetServerVersion() (*version.Version, error) } type clientWrapper struct { @@ -163,13 +163,13 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< } } - // If the kubernetes cluster is v1.18+, we can use the new IngressClass objects - major, minor, err := c.GetServerVersion() + serverVersion, err := c.GetServerVersion() if err != nil { - return nil, err + log.WithoutContext().Errorf("Failed to get server version: %v", err) + return eventCh, nil } - if major >= 1 && minor >= 18 { + if supportsIngressClass(serverVersion) { c.clusterFactory = informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod) c.clusterFactory.Networking().V1beta1().IngressClasses().Informer().AddEventHandler(eventHandler) c.clusterFactory.Start(stopCh) @@ -381,23 +381,13 @@ func (c *clientWrapper) newResourceEventHandler(events chan<- interface{}) cache } // GetServerVersion returns the cluster server version, or an error. -func (c *clientWrapper) GetServerVersion() (major, minor int, err error) { - version, err := c.clientset.Discovery().ServerVersion() +func (c *clientWrapper) GetServerVersion() (*version.Version, error) { + serverVersion, err := c.clientset.Discovery().ServerVersion() if err != nil { - return 0, 0, fmt.Errorf("could not determine cluster API version: %w", err) + return nil, fmt.Errorf("could not retrieve server version: %w", err) } - major, err = strconv.Atoi(version.Major) - if err != nil { - return 0, 0, fmt.Errorf("could not determine cluster major API version: %w", err) - } - - minor, err = strconv.Atoi(version.Minor) - if err != nil { - return 0, 0, fmt.Errorf("could not determine cluster minor API version: %w", err) - } - - return major, minor, nil + return version.NewVersion(serverVersion.GitVersion) } // eventHandlerFunc will pass the obj on to the events channel or drop it. @@ -432,3 +422,11 @@ func (c *clientWrapper) isWatchedNamespace(ns string) bool { } return false } + +// IngressClass objects are supported since Kubernetes v1.18. +// See https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class +func supportsIngressClass(serverVersion *version.Version) bool { + ingressClassVersion := version.Must(version.NewVersion("1.18")) + + return ingressClassVersion.LessThanOrEqual(serverVersion) +} diff --git a/pkg/provider/kubernetes/ingress/client_mock_test.go b/pkg/provider/kubernetes/ingress/client_mock_test.go index 6bf5716c1..410c96d52 100644 --- a/pkg/provider/kubernetes/ingress/client_mock_test.go +++ b/pkg/provider/kubernetes/ingress/client_mock_test.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "github.com/containous/traefik/v2/pkg/provider/kubernetes/k8s" + "github.com/hashicorp/go-version" corev1 "k8s.io/api/core/v1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" "k8s.io/api/networking/v1beta1" @@ -20,8 +21,7 @@ type clientMock struct { endpoints []*corev1.Endpoints ingressClass *networkingv1beta1.IngressClass - serverMajor int - serverMinor int + serverVersion *version.Version apiServiceError error apiSecretError error @@ -31,11 +31,10 @@ type clientMock struct { watchChan chan interface{} } -func newClientMock(major, minor int, paths ...string) clientMock { - c := clientMock{ - serverMajor: major, - serverMinor: minor, - } +func newClientMock(serverVersion string, paths ...string) clientMock { + c := clientMock{} + + c.serverVersion = version.Must(version.NewVersion(serverVersion)) for _, path := range paths { yamlContent, err := ioutil.ReadFile(path) @@ -75,8 +74,8 @@ func (c clientMock) GetIngresses() []*v1beta1.Ingress { return c.ingresses } -func (c clientMock) GetServerVersion() (major, minor int, err error) { - return c.serverMajor, c.serverMinor, nil +func (c clientMock) GetServerVersion() (*version.Version, error) { + return c.serverVersion, nil } func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) { diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index c6401e44e..b67251548 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -183,7 +183,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl TCP: &dynamic.TCPConfiguration{}, } - major, minor, err := client.GetServerVersion() + serverVersion, err := client.GetServerVersion() if err != nil { log.FromContext(ctx).Errorf("Failed to get server version: %v", err) return conf @@ -191,7 +191,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl var ingressClass *networkingv1beta1.IngressClass - if major >= 1 && minor >= 18 { + if supportsIngressClass(serverVersion) { ic, err := client.GetIngressClass() if err != nil { log.FromContext(ctx).Errorf("Failed to find an ingress class: %v", err) diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index 57e6d4794..a819ce16e 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -24,10 +24,10 @@ func Bool(v bool) *bool { return &v } func TestLoadConfigurationFromIngresses(t *testing.T) { testCases := []struct { - desc string - ingressClass string - serverMinor int - expected *dynamic.Configuration + desc string + ingressClass string + serverVersion string + expected *dynamic.Configuration }{ { desc: "Empty ingresses", @@ -925,8 +925,8 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { }, }, { - desc: "v18 Ingress with ingressClass", - serverMinor: 18, + desc: "v18 Ingress with ingressClass", + serverVersion: "v1.18", expected: &dynamic.Configuration{ TCP: &dynamic.TCPConfiguration{}, HTTP: &dynamic.HTTPConfiguration{ @@ -953,8 +953,8 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { }, }, { - desc: "v18 Ingress with missing ingressClass", - serverMinor: 18, + desc: "v18 Ingress with missing ingressClass", + serverVersion: "v1.18", expected: &dynamic.Configuration{ TCP: &dynamic.TCPConfiguration{}, HTTP: &dynamic.HTTPConfiguration{ @@ -993,12 +993,12 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { paths = append(paths, generateTestFilename("_ingressclass", test.desc)) } - serverMinor := 17 - if test.serverMinor != 0 { - serverMinor = test.serverMinor + serverVersion := test.serverVersion + if serverVersion == "" { + serverVersion = "v1.17" } - clientMock := newClientMock(1, serverMinor, paths...) + clientMock := newClientMock(serverVersion, paths...) p := Provider{IngressClass: test.ingressClass} conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)