Allow custom value for kubernetes.io/ingress.class annotation

This commit is contained in:
Yuvi Panda 2018-02-01 10:04:04 -08:00 committed by Traefiker
parent 52b4e93c38
commit 04ebd9d46a
4 changed files with 188 additions and 22 deletions

View file

@ -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

View file

@ -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

View file

@ -32,8 +32,8 @@ 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 {

View file

@ -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{ ObjectMeta: v1.ObjectMeta{
Name: "mySecret", Name: "mySecret",
UID: "1", UID: "1",
Namespace: "testing", 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(