Use semantic versioning to enable ingress class support
This commit is contained in:
parent
dcd0cda0c6
commit
a136c46148
4 changed files with 40 additions and 43 deletions
|
@ -5,11 +5,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/log"
|
"github.com/containous/traefik/v2/pkg/log"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/hashicorp/go-version"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||||
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
||||||
|
@ -55,7 +55,7 @@ type Client interface {
|
||||||
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
|
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
|
||||||
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
|
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
|
||||||
UpdateIngressStatus(ing *networkingv1beta1.Ingress, ip, hostname string) error
|
UpdateIngressStatus(ing *networkingv1beta1.Ingress, ip, hostname string) error
|
||||||
GetServerVersion() (major, minor int, err error)
|
GetServerVersion() (*version.Version, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientWrapper struct {
|
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
|
serverVersion, err := c.GetServerVersion()
|
||||||
major, minor, err := c.GetServerVersion()
|
|
||||||
if err != nil {
|
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 = informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod)
|
||||||
c.clusterFactory.Networking().V1beta1().IngressClasses().Informer().AddEventHandler(eventHandler)
|
c.clusterFactory.Networking().V1beta1().IngressClasses().Informer().AddEventHandler(eventHandler)
|
||||||
c.clusterFactory.Start(stopCh)
|
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.
|
// GetServerVersion returns the cluster server version, or an error.
|
||||||
func (c *clientWrapper) GetServerVersion() (major, minor int, err error) {
|
func (c *clientWrapper) GetServerVersion() (*version.Version, error) {
|
||||||
version, err := c.clientset.Discovery().ServerVersion()
|
serverVersion, err := c.clientset.Discovery().ServerVersion()
|
||||||
if err != nil {
|
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)
|
return version.NewVersion(serverVersion.GitVersion)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eventHandlerFunc will pass the obj on to the events channel or drop it.
|
// 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
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/provider/kubernetes/k8s"
|
"github.com/containous/traefik/v2/pkg/provider/kubernetes/k8s"
|
||||||
|
"github.com/hashicorp/go-version"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||||
"k8s.io/api/networking/v1beta1"
|
"k8s.io/api/networking/v1beta1"
|
||||||
|
@ -20,8 +21,7 @@ type clientMock struct {
|
||||||
endpoints []*corev1.Endpoints
|
endpoints []*corev1.Endpoints
|
||||||
ingressClass *networkingv1beta1.IngressClass
|
ingressClass *networkingv1beta1.IngressClass
|
||||||
|
|
||||||
serverMajor int
|
serverVersion *version.Version
|
||||||
serverMinor int
|
|
||||||
|
|
||||||
apiServiceError error
|
apiServiceError error
|
||||||
apiSecretError error
|
apiSecretError error
|
||||||
|
@ -31,11 +31,10 @@ type clientMock struct {
|
||||||
watchChan chan interface{}
|
watchChan chan interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClientMock(major, minor int, paths ...string) clientMock {
|
func newClientMock(serverVersion string, paths ...string) clientMock {
|
||||||
c := clientMock{
|
c := clientMock{}
|
||||||
serverMajor: major,
|
|
||||||
serverMinor: minor,
|
c.serverVersion = version.Must(version.NewVersion(serverVersion))
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
yamlContent, err := ioutil.ReadFile(path)
|
yamlContent, err := ioutil.ReadFile(path)
|
||||||
|
@ -75,8 +74,8 @@ func (c clientMock) GetIngresses() []*v1beta1.Ingress {
|
||||||
return c.ingresses
|
return c.ingresses
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c clientMock) GetServerVersion() (major, minor int, err error) {
|
func (c clientMock) GetServerVersion() (*version.Version, error) {
|
||||||
return c.serverMajor, c.serverMinor, nil
|
return c.serverVersion, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) {
|
func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) {
|
||||||
|
|
|
@ -183,7 +183,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||||
TCP: &dynamic.TCPConfiguration{},
|
TCP: &dynamic.TCPConfiguration{},
|
||||||
}
|
}
|
||||||
|
|
||||||
major, minor, err := client.GetServerVersion()
|
serverVersion, err := client.GetServerVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromContext(ctx).Errorf("Failed to get server version: %v", err)
|
log.FromContext(ctx).Errorf("Failed to get server version: %v", err)
|
||||||
return conf
|
return conf
|
||||||
|
@ -191,7 +191,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||||
|
|
||||||
var ingressClass *networkingv1beta1.IngressClass
|
var ingressClass *networkingv1beta1.IngressClass
|
||||||
|
|
||||||
if major >= 1 && minor >= 18 {
|
if supportsIngressClass(serverVersion) {
|
||||||
ic, err := client.GetIngressClass()
|
ic, err := client.GetIngressClass()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromContext(ctx).Errorf("Failed to find an ingress class: %v", err)
|
log.FromContext(ctx).Errorf("Failed to find an ingress class: %v", err)
|
||||||
|
|
|
@ -24,10 +24,10 @@ func Bool(v bool) *bool { return &v }
|
||||||
|
|
||||||
func TestLoadConfigurationFromIngresses(t *testing.T) {
|
func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
ingressClass string
|
ingressClass string
|
||||||
serverMinor int
|
serverVersion string
|
||||||
expected *dynamic.Configuration
|
expected *dynamic.Configuration
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "Empty ingresses",
|
desc: "Empty ingresses",
|
||||||
|
@ -925,8 +925,8 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "v18 Ingress with ingressClass",
|
desc: "v18 Ingress with ingressClass",
|
||||||
serverMinor: 18,
|
serverVersion: "v1.18",
|
||||||
expected: &dynamic.Configuration{
|
expected: &dynamic.Configuration{
|
||||||
TCP: &dynamic.TCPConfiguration{},
|
TCP: &dynamic.TCPConfiguration{},
|
||||||
HTTP: &dynamic.HTTPConfiguration{
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
@ -953,8 +953,8 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "v18 Ingress with missing ingressClass",
|
desc: "v18 Ingress with missing ingressClass",
|
||||||
serverMinor: 18,
|
serverVersion: "v1.18",
|
||||||
expected: &dynamic.Configuration{
|
expected: &dynamic.Configuration{
|
||||||
TCP: &dynamic.TCPConfiguration{},
|
TCP: &dynamic.TCPConfiguration{},
|
||||||
HTTP: &dynamic.HTTPConfiguration{
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
@ -993,12 +993,12 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
paths = append(paths, generateTestFilename("_ingressclass", test.desc))
|
paths = append(paths, generateTestFilename("_ingressclass", test.desc))
|
||||||
}
|
}
|
||||||
|
|
||||||
serverMinor := 17
|
serverVersion := test.serverVersion
|
||||||
if test.serverMinor != 0 {
|
if serverVersion == "" {
|
||||||
serverMinor = test.serverMinor
|
serverVersion = "v1.17"
|
||||||
}
|
}
|
||||||
|
|
||||||
clientMock := newClientMock(1, serverMinor, paths...)
|
clientMock := newClientMock(serverVersion, paths...)
|
||||||
|
|
||||||
p := Provider{IngressClass: test.ingressClass}
|
p := Provider{IngressClass: test.ingressClass}
|
||||||
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
|
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
|
||||||
|
|
Loading…
Reference in a new issue