Allow custom value for kubernetes.io/ingress.class annotation
This commit is contained in:
parent
52b4e93c38
commit
04ebd9d46a
4 changed files with 188 additions and 22 deletions
|
@ -130,8 +130,6 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
//default Kubernetes
|
//default Kubernetes
|
||||||
var defaultKubernetes kubernetes.Provider
|
var defaultKubernetes kubernetes.Provider
|
||||||
defaultKubernetes.Watch = true
|
defaultKubernetes.Watch = true
|
||||||
defaultKubernetes.Endpoint = ""
|
|
||||||
defaultKubernetes.LabelSelector = ""
|
|
||||||
defaultKubernetes.Constraints = types.Constraints{}
|
defaultKubernetes.Constraints = types.Constraints{}
|
||||||
|
|
||||||
// default Mesos
|
// default Mesos
|
||||||
|
|
|
@ -50,6 +50,17 @@ See also [Kubernetes user guide](/user-guide/kubernetes).
|
||||||
#
|
#
|
||||||
# labelselector = "A and not B"
|
# labelselector = "A and not B"
|
||||||
|
|
||||||
|
# Value of `kubernetes.io/ingress.class` annotation that identifies Ingress objects to be processed.
|
||||||
|
# If the parameter is non-empty, only Ingresses containing an annotation with the same value are processed.
|
||||||
|
# Otherwise, Ingresses missing the annotation, having an empty value, or the value `traefik` are processed.
|
||||||
|
#
|
||||||
|
# Note : `ingressClass` option must begin with the "traefik" prefix.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: empty
|
||||||
|
#
|
||||||
|
# ingressClass = "traefik-internal"
|
||||||
|
|
||||||
# Disable PassHost Headers.
|
# Disable PassHost Headers.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
|
|
|
@ -30,10 +30,10 @@ import (
|
||||||
var _ provider.Provider = (*Provider)(nil)
|
var _ provider.Provider = (*Provider)(nil)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ruleTypePathPrefix = "PathPrefix"
|
ruleTypePathPrefix = "PathPrefix"
|
||||||
ruleTypeReplacePath = "ReplacePath"
|
ruleTypeReplacePath = "ReplacePath"
|
||||||
|
traefikDefaultRealm = "traefik"
|
||||||
traefikDefaultRealm = "traefik"
|
traefikDefaultIngressClass = "traefik"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider holds configurations of the provider.
|
// Provider holds configurations of the provider.
|
||||||
|
@ -46,6 +46,7 @@ type Provider struct {
|
||||||
EnablePassTLSCert bool `description:"Kubernetes enable Pass TLS Client Certs" export:"true"`
|
EnablePassTLSCert bool `description:"Kubernetes enable Pass TLS Client Certs" export:"true"`
|
||||||
Namespaces Namespaces `description:"Kubernetes namespaces" export:"true"`
|
Namespaces Namespaces `description:"Kubernetes namespaces" export:"true"`
|
||||||
LabelSelector string `description:"Kubernetes api label selector to use" export:"true"`
|
LabelSelector string `description:"Kubernetes api label selector to use" export:"true"`
|
||||||
|
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for" export:"true"`
|
||||||
lastConfiguration safe.Safe
|
lastConfiguration safe.Safe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +77,12 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We require that IngressClasses start with `traefik` to reduce chances of
|
||||||
|
// conflict with other Ingress Providers
|
||||||
|
if len(p.IngressClass) > 0 && !strings.HasPrefix(p.IngressClass, traefikDefaultIngressClass) {
|
||||||
|
return fmt.Errorf("value for IngressClass has to be empty or start with the prefix %q, instead found %q", traefikDefaultIngressClass, p.IngressClass)
|
||||||
|
}
|
||||||
|
|
||||||
k8sClient, err := p.newK8sClient()
|
k8sClient, err := p.newK8sClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -147,7 +154,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
||||||
annotationIngressClass := getAnnotationName(i.Annotations, annotationKubernetesIngressClass)
|
annotationIngressClass := getAnnotationName(i.Annotations, annotationKubernetesIngressClass)
|
||||||
ingressClass := i.Annotations[annotationIngressClass]
|
ingressClass := i.Annotations[annotationIngressClass]
|
||||||
|
|
||||||
if !shouldProcessIngress(ingressClass) {
|
if !p.shouldProcessIngress(ingressClass) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,8 +458,11 @@ func equalPorts(servicePort v1.ServicePort, ingressPort intstr.IntOrString) bool
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldProcessIngress(ingressClass string) bool {
|
func (p *Provider) shouldProcessIngress(annotationIngressClass string) bool {
|
||||||
return ingressClass == "" || ingressClass == "traefik"
|
if len(p.IngressClass) == 0 {
|
||||||
|
return len(annotationIngressClass) == 0 || annotationIngressClass == traefikDefaultIngressClass
|
||||||
|
}
|
||||||
|
return annotationIngressClass == p.IngressClass
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFrontendRedirect(i *v1beta1.Ingress) *types.Redirect {
|
func getFrontendRedirect(i *v1beta1.Ingress) *types.Redirect {
|
||||||
|
|
|
@ -616,7 +616,7 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
buildIngress(
|
buildIngress(
|
||||||
iNamespace("testing"),
|
iNamespace("testing"),
|
||||||
iAnnotation(annotationKubernetesPreserveHost, "true"),
|
iAnnotation(annotationKubernetesPreserveHost, "true"),
|
||||||
iAnnotation(annotationKubernetesIngressClass, "traefik"),
|
iAnnotation(annotationKubernetesIngressClass, traefikDefaultRealm),
|
||||||
iRules(
|
iRules(
|
||||||
iRule(
|
iRule(
|
||||||
iHost("other"),
|
iHost("other"),
|
||||||
|
@ -626,7 +626,7 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
buildIngress(
|
buildIngress(
|
||||||
iNamespace("testing"),
|
iNamespace("testing"),
|
||||||
iAnnotation(annotationKubernetesPassTLSCert, "true"),
|
iAnnotation(annotationKubernetesPassTLSCert, "true"),
|
||||||
iAnnotation(annotationKubernetesIngressClass, "traefik"),
|
iAnnotation(annotationKubernetesIngressClass, traefikDefaultRealm),
|
||||||
iRules(
|
iRules(
|
||||||
iRule(
|
iRule(
|
||||||
iHost("other"),
|
iHost("other"),
|
||||||
|
@ -636,7 +636,7 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
buildIngress(
|
buildIngress(
|
||||||
iNamespace("testing"),
|
iNamespace("testing"),
|
||||||
iAnnotation(annotationKubernetesFrontendEntryPoints, "http,https"),
|
iAnnotation(annotationKubernetesFrontendEntryPoints, "http,https"),
|
||||||
iAnnotation(annotationKubernetesIngressClass, "traefik"),
|
iAnnotation(annotationKubernetesIngressClass, traefikDefaultRealm),
|
||||||
iRules(
|
iRules(
|
||||||
iRule(
|
iRule(
|
||||||
iHost("other"),
|
iHost("other"),
|
||||||
|
@ -655,7 +655,7 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
),
|
),
|
||||||
buildIngress(
|
buildIngress(
|
||||||
iNamespace("testing"),
|
iNamespace("testing"),
|
||||||
iAnnotation(annotationKubernetesIngressClass, "somethingOtherThanTraefik"),
|
iAnnotation(annotationKubernetesIngressClass, traefikDefaultRealm+"-other"),
|
||||||
iRules(
|
iRules(
|
||||||
iRule(
|
iRule(
|
||||||
iHost("herp"),
|
iHost("herp"),
|
||||||
|
@ -664,7 +664,6 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
),
|
),
|
||||||
buildIngress(
|
buildIngress(
|
||||||
iNamespace("testing"),
|
iNamespace("testing"),
|
||||||
iAnnotation(annotationKubernetesIngressClass, "traefik"),
|
|
||||||
iAnnotation(annotationKubernetesWhitelistSourceRange, "1.1.1.1/24, 1234:abcd::42/32"),
|
iAnnotation(annotationKubernetesWhitelistSourceRange, "1.1.1.1/24, 1234:abcd::42/32"),
|
||||||
iRules(
|
iRules(
|
||||||
iRule(
|
iRule(
|
||||||
|
@ -692,7 +691,6 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
),
|
),
|
||||||
buildIngress(
|
buildIngress(
|
||||||
iNamespace("testing"),
|
iNamespace("testing"),
|
||||||
iAnnotation(annotationKubernetesIngressClass, "traefik"),
|
|
||||||
iAnnotation(annotationKubernetesRedirectEntryPoint, "https"),
|
iAnnotation(annotationKubernetesRedirectEntryPoint, "https"),
|
||||||
iRules(
|
iRules(
|
||||||
iRule(
|
iRule(
|
||||||
|
@ -790,14 +788,16 @@ rateset:
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets := []*v1.Secret{{
|
secrets := []*v1.Secret{
|
||||||
ObjectMeta: v1.ObjectMeta{
|
{
|
||||||
Name: "mySecret",
|
ObjectMeta: v1.ObjectMeta{
|
||||||
UID: "1",
|
Name: "mySecret",
|
||||||
Namespace: "testing",
|
UID: "1",
|
||||||
|
Namespace: "testing",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{"auth": []byte("myUser:myEncodedPW")},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{"auth": []byte("myUser:myEncodedPW")},
|
}
|
||||||
}}
|
|
||||||
|
|
||||||
watchChan := make(chan interface{})
|
watchChan := make(chan interface{})
|
||||||
client := clientMock{
|
client := clientMock{
|
||||||
|
@ -999,6 +999,153 @@ rateset:
|
||||||
assert.Equal(t, expected, actual)
|
assert.Equal(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIngressClassAnnotation(t *testing.T) {
|
||||||
|
ingresses := []*v1beta1.Ingress{
|
||||||
|
buildIngress(
|
||||||
|
iNamespace("testing"),
|
||||||
|
iAnnotation(annotationKubernetesIngressClass, traefikDefaultIngressClass),
|
||||||
|
iRules(
|
||||||
|
iRule(
|
||||||
|
iHost("other"),
|
||||||
|
iPaths(onePath(iPath("/stuff"), iBackend("service1", intstr.FromInt(80))))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
buildIngress(
|
||||||
|
iNamespace("testing"),
|
||||||
|
iAnnotation(annotationKubernetesIngressClass, ""),
|
||||||
|
iRules(
|
||||||
|
iRule(
|
||||||
|
iHost("other"),
|
||||||
|
iPaths(onePath(iPath("/sslstuff"), iBackend("service1", intstr.FromInt(80))))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
buildIngress(
|
||||||
|
iNamespace("testing"),
|
||||||
|
iRules(
|
||||||
|
iRule(
|
||||||
|
iHost("other"),
|
||||||
|
iPaths(onePath(iPath("/"), iBackend("service1", intstr.FromInt(80))))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
buildIngress(
|
||||||
|
iNamespace("testing"),
|
||||||
|
iAnnotation(annotationKubernetesIngressClass, traefikDefaultIngressClass+"-other"),
|
||||||
|
iRules(
|
||||||
|
iRule(
|
||||||
|
iHost("herp"),
|
||||||
|
iPaths(onePath(iPath("/derp"), iBackend("service1", intstr.FromInt(80))))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
services := []*v1.Service{
|
||||||
|
buildService(
|
||||||
|
sName("service1"),
|
||||||
|
sNamespace("testing"),
|
||||||
|
sUID("1"),
|
||||||
|
sSpec(
|
||||||
|
clusterIP("10.0.0.1"),
|
||||||
|
sType("ExternalName"),
|
||||||
|
sExternalName("example.com"),
|
||||||
|
sPorts(sPort(80, "http"))),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
watchChan := make(chan interface{})
|
||||||
|
client := clientMock{
|
||||||
|
ingresses: ingresses,
|
||||||
|
services: services,
|
||||||
|
watchChan: watchChan,
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
provider Provider
|
||||||
|
expected *types.Configuration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Empty IngressClass annotation",
|
||||||
|
provider: Provider{},
|
||||||
|
expected: buildConfiguration(
|
||||||
|
backends(
|
||||||
|
backend("other/stuff",
|
||||||
|
servers(
|
||||||
|
server("http://example.com", weight(1)),
|
||||||
|
server("http://example.com", weight(1))),
|
||||||
|
lbMethod("wrr"),
|
||||||
|
),
|
||||||
|
backend("other/",
|
||||||
|
servers(
|
||||||
|
server("http://example.com", weight(1)),
|
||||||
|
server("http://example.com", weight(1))),
|
||||||
|
lbMethod("wrr"),
|
||||||
|
),
|
||||||
|
backend("other/sslstuff",
|
||||||
|
servers(
|
||||||
|
server("http://example.com", weight(1)),
|
||||||
|
server("http://example.com", weight(1))),
|
||||||
|
lbMethod("wrr"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
frontends(
|
||||||
|
frontend("other/stuff",
|
||||||
|
passHostHeader(),
|
||||||
|
routes(
|
||||||
|
route("/stuff", "PathPrefix:/stuff"),
|
||||||
|
route("other", "Host:other")),
|
||||||
|
),
|
||||||
|
frontend("other/",
|
||||||
|
passHostHeader(),
|
||||||
|
routes(
|
||||||
|
route("/", "PathPrefix:/"),
|
||||||
|
route("other", "Host:other")),
|
||||||
|
),
|
||||||
|
frontend("other/sslstuff",
|
||||||
|
passHostHeader(),
|
||||||
|
routes(
|
||||||
|
route("/sslstuff", "PathPrefix:/sslstuff"),
|
||||||
|
route("other", "Host:other")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Provided IngressClass annotation",
|
||||||
|
provider: Provider{IngressClass: traefikDefaultRealm + "-other"},
|
||||||
|
expected: buildConfiguration(
|
||||||
|
backends(
|
||||||
|
backend("herp/derp",
|
||||||
|
servers(
|
||||||
|
server("http://example.com", weight(1)),
|
||||||
|
server("http://example.com", weight(1))),
|
||||||
|
lbMethod("wrr"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
frontends(
|
||||||
|
frontend("herp/derp",
|
||||||
|
passHostHeader(),
|
||||||
|
routes(
|
||||||
|
route("/derp", "PathPrefix:/derp"),
|
||||||
|
route("herp", "Host:herp")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
actual, err := test.provider.loadIngresses(client)
|
||||||
|
require.NoError(t, err, "error loading ingresses")
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPriorityHeaderValue(t *testing.T) {
|
func TestPriorityHeaderValue(t *testing.T) {
|
||||||
ingresses := []*v1beta1.Ingress{
|
ingresses := []*v1beta1.Ingress{
|
||||||
buildIngress(
|
buildIngress(
|
||||||
|
|
Loading…
Reference in a new issue