diff --git a/docs/content/middlewares/chain.md b/docs/content/middlewares/chain.md index 8ad38f7dd..3d7b6d53c 100644 --- a/docs/content/middlewares/chain.md +++ b/docs/content/middlewares/chain.md @@ -51,9 +51,9 @@ metadata: spec: chain: middlewares: - - https-only - - known-ips - - auth-users + - name: https-only + - name: known-ips + - name: auth-users --- apiVersion: traefik.containo.us/v1alpha1 kind: Middleware diff --git a/integration/fixtures/k8s/04-ingressroute.yml b/integration/fixtures/k8s/04-ingressroute.yml index 596073206..85643e638 100644 --- a/integration/fixtures/k8s/04-ingressroute.yml +++ b/integration/fixtures/k8s/04-ingressroute.yml @@ -1,5 +1,17 @@ apiVersion: traefik.containo.us/v1alpha1 kind: Middleware +metadata: + name: mychain + namespace: default + +spec: + chain: + middlewares: + - name: stripprefix + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware metadata: name: stripprefix namespace: default @@ -26,4 +38,4 @@ spec: - name: whoami port: 80 middlewares: - - name: stripprefix + - name: mychain diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index 696e34dd2..5bf6512a4 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -20,7 +20,7 @@ "web" ], "middlewares": [ - "default/stripprefix" + "default/mychain" ], "service": "default/test2.route-23c7f4c450289ee29016", "rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/tobestripped`)", @@ -31,16 +31,24 @@ } }, "middlewares": { - "default/stripprefix@kubernetescrd": { - "stripPrefix": { - "prefixes": [ - "/tobestripped" + "default/mychain@kubernetescrd": { + "chain": { + "middlewares": [ + "default/stripprefix" ] }, "status": "enabled", "usedBy": [ "default/test2.route-23c7f4c450289ee29016@kubernetescrd" ] + }, + "default/stripprefix@kubernetescrd": { + "stripPrefix": { + "prefixes": [ + "/tobestripped" + ] + }, + "status": "enabled" } }, "services": { @@ -48,10 +56,10 @@ "loadBalancer": { "servers": [ { - "url": "http://10.42.0.2:80" + "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.6:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true @@ -61,18 +69,18 @@ "default/test.route-6b204d94623b3df4370c@kubernetescrd" ], "serverStatus": { - "http://10.42.0.2:80": "UP", - "http://10.42.0.6:80": "UP" + "http://10.42.0.3:80": "UP", + "http://10.42.0.5:80": "UP" } }, "default/test2.route-23c7f4c450289ee29016@kubernetescrd": { "loadBalancer": { "servers": [ { - "url": "http://10.42.0.2:80" + "url": "http://10.42.0.3:80" }, { - "url": "http://10.42.0.6:80" + "url": "http://10.42.0.5:80" } ], "passHostHeader": true @@ -82,8 +90,8 @@ "default/test2.route-23c7f4c450289ee29016@kubernetescrd" ], "serverStatus": { - "http://10.42.0.2:80": "UP", - "http://10.42.0.6:80": "UP" + "http://10.42.0.3:80": "UP", + "http://10.42.0.5:80": "UP" } } }, @@ -109,10 +117,10 @@ "loadBalancer": { "servers": [ { - "address": "10.42.0.4:8080" + "address": "10.42.0.6:8080" }, { - "address": "10.42.0.5:8080" + "address": "10.42.0.7:8080" } ] }, diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index f3307ab8d..6296463a6 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -15,6 +15,7 @@ import ( "github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/job" "github.com/containous/traefik/v2/pkg/log" + "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" "github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/tls" corev1 "k8s.io/api/core/v1" @@ -146,12 +147,62 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) } for _, middleware := range client.GetMiddlewares() { - conf.HTTP.Middlewares[makeID(middleware.Namespace, middleware.Name)] = &middleware.Spec + id := makeID(middleware.Namespace, middleware.Name) + ctxMid := log.With(ctx, log.Str(log.MiddlewareName, id)) + conf.HTTP.Middlewares[id] = &dynamic.Middleware{ + AddPrefix: middleware.Spec.AddPrefix, + StripPrefix: middleware.Spec.StripPrefix, + StripPrefixRegex: middleware.Spec.StripPrefixRegex, + ReplacePath: middleware.Spec.ReplacePath, + ReplacePathRegex: middleware.Spec.ReplacePathRegex, + Chain: createChainMiddleware(ctxMid, middleware.Namespace, middleware.Spec.Chain), + IPWhiteList: middleware.Spec.IPWhiteList, + Headers: middleware.Spec.Headers, + Errors: middleware.Spec.Errors, + RateLimit: middleware.Spec.RateLimit, + RedirectRegex: middleware.Spec.RedirectRegex, + RedirectScheme: middleware.Spec.RedirectScheme, + BasicAuth: middleware.Spec.BasicAuth, + DigestAuth: middleware.Spec.DigestAuth, + ForwardAuth: middleware.Spec.ForwardAuth, + InFlightReq: middleware.Spec.InFlightReq, + Buffering: middleware.Spec.Buffering, + CircuitBreaker: middleware.Spec.CircuitBreaker, + Compress: middleware.Spec.Compress, + PassTLSClientCert: middleware.Spec.PassTLSClientCert, + Retry: middleware.Spec.Retry, + } + } return conf } +func createChainMiddleware(ctx context.Context, namespace string, chain *v1alpha1.Chain) *dynamic.Chain { + if chain == nil { + return nil + } + + var mds []string + for _, mi := range chain.Middlewares { + if strings.Contains(mi.Name, "@") { + if len(mi.Namespace) > 0 { + log.FromContext(ctx). + Warnf("namespace %q is ignored in cross-provider context", mi.Namespace) + } + mds = append(mds, mi.Name) + continue + } + + ns := mi.Namespace + if len(ns) == 0 { + ns = namespace + } + mds = append(mds, makeID(ns, mi.Name)) + } + return &dynamic.Chain{Middlewares: mds} +} + func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options { tlsOptionsCRD := client.GetTLSOptions() var tlsOptions map[string]tls.Options diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go index c01f12c99..c5c2c4154 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go @@ -13,7 +13,41 @@ type Middleware struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` - Spec dynamic.Middleware `json:"spec"` + Spec MiddlewareSpec `json:"spec"` +} + +// +k8s:deepcopy-gen=true + +// Middleware holds the Middleware configuration. +type MiddlewareSpec struct { + AddPrefix *dynamic.AddPrefix `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty"` + StripPrefix *dynamic.StripPrefix `json:"stripPrefix,omitempty" toml:"stripPrefix,omitempty" yaml:"stripPrefix,omitempty"` + StripPrefixRegex *dynamic.StripPrefixRegex `json:"stripPrefixRegex,omitempty" toml:"stripPrefixRegex,omitempty" yaml:"stripPrefixRegex,omitempty"` + ReplacePath *dynamic.ReplacePath `json:"replacePath,omitempty" toml:"replacePath,omitempty" yaml:"replacePath,omitempty"` + ReplacePathRegex *dynamic.ReplacePathRegex `json:"replacePathRegex,omitempty" toml:"replacePathRegex,omitempty" yaml:"replacePathRegex,omitempty"` + Chain *Chain `json:"chain,omitempty" toml:"chain,omitempty" yaml:"chain,omitempty"` + IPWhiteList *dynamic.IPWhiteList `json:"ipWhiteList,omitempty" toml:"ipWhiteList,omitempty" yaml:"ipWhiteList,omitempty"` + Headers *dynamic.Headers `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` + Errors *dynamic.ErrorPage `json:"errors,omitempty" toml:"errors,omitempty" yaml:"errors,omitempty"` + RateLimit *dynamic.RateLimit `json:"rateLimit,omitempty" toml:"rateLimit,omitempty" yaml:"rateLimit,omitempty"` + RedirectRegex *dynamic.RedirectRegex `json:"redirectRegex,omitempty" toml:"redirectRegex,omitempty" yaml:"redirectRegex,omitempty"` + RedirectScheme *dynamic.RedirectScheme `json:"redirectScheme,omitempty" toml:"redirectScheme,omitempty" yaml:"redirectScheme,omitempty"` + BasicAuth *dynamic.BasicAuth `json:"basicAuth,omitempty" toml:"basicAuth,omitempty" yaml:"basicAuth,omitempty"` + DigestAuth *dynamic.DigestAuth `json:"digestAuth,omitempty" toml:"digestAuth,omitempty" yaml:"digestAuth,omitempty"` + ForwardAuth *dynamic.ForwardAuth `json:"forwardAuth,omitempty" toml:"forwardAuth,omitempty" yaml:"forwardAuth,omitempty"` + InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty" toml:"inFlightReq,omitempty" yaml:"inFlightReq,omitempty"` + Buffering *dynamic.Buffering `json:"buffering,omitempty" toml:"buffering,omitempty" yaml:"buffering,omitempty"` + CircuitBreaker *dynamic.CircuitBreaker `json:"circuitBreaker,omitempty" toml:"circuitBreaker,omitempty" yaml:"circuitBreaker,omitempty"` + Compress *dynamic.Compress `json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" label:"allowEmpty"` + PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty" toml:"passTLSClientCert,omitempty" yaml:"passTLSClientCert,omitempty"` + Retry *dynamic.Retry `json:"retry,omitempty" toml:"retry,omitempty" yaml:"retry,omitempty"` +} + +// +k8s:deepcopy-gen=true + +// Chain holds a chain of middlewares +type Chain struct { + Middlewares []MiddlewareRef `json:"middlewares,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go index 047f0fcaa..67aa55d0c 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go @@ -29,9 +29,31 @@ THE SOFTWARE. package v1alpha1 import ( + dynamic "github.com/containous/traefik/v2/pkg/config/dynamic" runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Chain) DeepCopyInto(out *Chain) { + *out = *in + if in.Middlewares != nil { + in, out := &in.Middlewares, &out.Middlewares + *out = make([]MiddlewareRef, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Chain. +func (in *Chain) DeepCopy() *Chain { + if in == nil { + return nil + } + out := new(Chain) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClientAuth) DeepCopyInto(out *ClientAuth) { *out = *in @@ -338,6 +360,127 @@ func (in *MiddlewareRef) DeepCopy() *MiddlewareRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) { + *out = *in + if in.AddPrefix != nil { + in, out := &in.AddPrefix, &out.AddPrefix + *out = new(dynamic.AddPrefix) + **out = **in + } + if in.StripPrefix != nil { + in, out := &in.StripPrefix, &out.StripPrefix + *out = new(dynamic.StripPrefix) + (*in).DeepCopyInto(*out) + } + if in.StripPrefixRegex != nil { + in, out := &in.StripPrefixRegex, &out.StripPrefixRegex + *out = new(dynamic.StripPrefixRegex) + (*in).DeepCopyInto(*out) + } + if in.ReplacePath != nil { + in, out := &in.ReplacePath, &out.ReplacePath + *out = new(dynamic.ReplacePath) + **out = **in + } + if in.ReplacePathRegex != nil { + in, out := &in.ReplacePathRegex, &out.ReplacePathRegex + *out = new(dynamic.ReplacePathRegex) + **out = **in + } + if in.Chain != nil { + in, out := &in.Chain, &out.Chain + *out = new(Chain) + (*in).DeepCopyInto(*out) + } + if in.IPWhiteList != nil { + in, out := &in.IPWhiteList, &out.IPWhiteList + *out = new(dynamic.IPWhiteList) + (*in).DeepCopyInto(*out) + } + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = new(dynamic.Headers) + (*in).DeepCopyInto(*out) + } + if in.Errors != nil { + in, out := &in.Errors, &out.Errors + *out = new(dynamic.ErrorPage) + (*in).DeepCopyInto(*out) + } + if in.RateLimit != nil { + in, out := &in.RateLimit, &out.RateLimit + *out = new(dynamic.RateLimit) + (*in).DeepCopyInto(*out) + } + if in.RedirectRegex != nil { + in, out := &in.RedirectRegex, &out.RedirectRegex + *out = new(dynamic.RedirectRegex) + **out = **in + } + if in.RedirectScheme != nil { + in, out := &in.RedirectScheme, &out.RedirectScheme + *out = new(dynamic.RedirectScheme) + **out = **in + } + if in.BasicAuth != nil { + in, out := &in.BasicAuth, &out.BasicAuth + *out = new(dynamic.BasicAuth) + (*in).DeepCopyInto(*out) + } + if in.DigestAuth != nil { + in, out := &in.DigestAuth, &out.DigestAuth + *out = new(dynamic.DigestAuth) + (*in).DeepCopyInto(*out) + } + if in.ForwardAuth != nil { + in, out := &in.ForwardAuth, &out.ForwardAuth + *out = new(dynamic.ForwardAuth) + (*in).DeepCopyInto(*out) + } + if in.InFlightReq != nil { + in, out := &in.InFlightReq, &out.InFlightReq + *out = new(dynamic.InFlightReq) + (*in).DeepCopyInto(*out) + } + if in.Buffering != nil { + in, out := &in.Buffering, &out.Buffering + *out = new(dynamic.Buffering) + **out = **in + } + if in.CircuitBreaker != nil { + in, out := &in.CircuitBreaker, &out.CircuitBreaker + *out = new(dynamic.CircuitBreaker) + **out = **in + } + if in.Compress != nil { + in, out := &in.Compress, &out.Compress + *out = new(dynamic.Compress) + **out = **in + } + if in.PassTLSClientCert != nil { + in, out := &in.PassTLSClientCert, &out.PassTLSClientCert + *out = new(dynamic.PassTLSClientCert) + (*in).DeepCopyInto(*out) + } + if in.Retry != nil { + in, out := &in.Retry, &out.Retry + *out = new(dynamic.Retry) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MiddlewareSpec. +func (in *MiddlewareSpec) DeepCopy() *MiddlewareSpec { + if in == nil { + return nil + } + out := new(MiddlewareSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Route) DeepCopyInto(out *Route) { *out = *in