k8s ErrorPage middleware now uses k8s service

This commit is contained in:
Julien Salleyron 2019-09-10 17:24:03 +02:00 committed by Traefiker Bot
parent 34be181706
commit fb8edd86d5
7 changed files with 130 additions and 8 deletions

View file

@ -29,8 +29,10 @@ spec:
errors: errors:
status: status:
- 500-599 - 500-599
service: serviceError
query: /{status}.html query: /{status}.html
service:
name: whoami
port: 80
``` ```
```json tab="Marathon" ```json tab="Marathon"
@ -95,6 +97,9 @@ The status code ranges are inclusive (`500-599` will trigger with every code bet
The service that will serve the new requested error page. The service that will serve the new requested error page.
!!! Note
In kubernetes, you need to reference a kubernetes service instead of a traefik service.
### `query` ### `query`
The URL for the error page (hosted by `service`). You can use `{status}` in the query, that will be replaced by the received status code. The URL for the error page (hosted by `service`). You can use `{status}` in the query, that will be replaced by the received status code.

View file

@ -0,0 +1,15 @@
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: errorpage
namespace: default
spec:
errors:
status:
- "404"
- "500"
query: query
service:
name: whoami
port: 80

View file

@ -170,6 +170,18 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
continue continue
} }
errorPage, errorPageService, err := createErrorPageMiddleware(client, middleware.Namespace, middleware.Spec.Errors)
if err != nil {
log.FromContext(ctxMid).Errorf("Error while reading error page middleware: %v", err)
continue
}
if errorPage != nil && errorPageService != nil {
serviceName := id + "-errorpage-service"
errorPage.Service = serviceName
conf.HTTP.Services[serviceName] = errorPageService
}
conf.HTTP.Middlewares[id] = &dynamic.Middleware{ conf.HTTP.Middlewares[id] = &dynamic.Middleware{
AddPrefix: middleware.Spec.AddPrefix, AddPrefix: middleware.Spec.AddPrefix,
StripPrefix: middleware.Spec.StripPrefix, StripPrefix: middleware.Spec.StripPrefix,
@ -179,7 +191,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
Chain: createChainMiddleware(ctxMid, middleware.Namespace, middleware.Spec.Chain), Chain: createChainMiddleware(ctxMid, middleware.Namespace, middleware.Spec.Chain),
IPWhiteList: middleware.Spec.IPWhiteList, IPWhiteList: middleware.Spec.IPWhiteList,
Headers: middleware.Spec.Headers, Headers: middleware.Spec.Headers,
Errors: middleware.Spec.Errors, Errors: errorPage,
RateLimit: middleware.Spec.RateLimit, RateLimit: middleware.Spec.RateLimit,
RedirectRegex: middleware.Spec.RedirectRegex, RedirectRegex: middleware.Spec.RedirectRegex,
RedirectScheme: middleware.Spec.RedirectScheme, RedirectScheme: middleware.Spec.RedirectScheme,
@ -199,6 +211,24 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
return conf return conf
} }
func createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) {
if errorPage == nil {
return nil, nil, nil
}
errorPageMiddleware := &dynamic.ErrorPage{
Status: errorPage.Status,
Query: errorPage.Query,
}
balancerServerHTTP, err := createLoadBalancerServerHTTP(client, namespace, errorPage.Service)
if err != nil {
return nil, nil, err
}
return errorPageMiddleware, balancerServerHTTP, nil
}
func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *v1alpha1.ForwardAuth) (*dynamic.ForwardAuth, error) { func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *v1alpha1.ForwardAuth) (*dynamic.ForwardAuth, error) {
if auth == nil { if auth == nil {
return nil, nil return nil, nil

View file

@ -64,7 +64,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
serviceName := makeID(ingressRoute.Namespace, key) serviceName := makeID(ingressRoute.Namespace, key)
for _, service := range route.Services { for _, service := range route.Services {
balancerServerHTTP, err := createLoadBalancerServerHTTP(client, ingressRoute, service) balancerServerHTTP, err := createLoadBalancerServerHTTP(client, ingressRoute.Namespace, service)
if err != nil { if err != nil {
logger. logger.
WithField("serviceName", service.Name). WithField("serviceName", service.Name).
@ -151,8 +151,8 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
return conf return conf
} }
func createLoadBalancerServerHTTP(client Client, ingressRoute *v1alpha1.IngressRoute, service v1alpha1.Service) (*dynamic.Service, error) { func createLoadBalancerServerHTTP(client Client, namespace string, service v1alpha1.Service) (*dynamic.Service, error) {
servers, err := loadServers(client, ingressRoute.Namespace, service) servers, err := loadServers(client, namespace, service)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -181,7 +181,7 @@ func loadServers(client Client, namespace string, svc v1alpha1.Service) ([]dynam
} }
if !exists { if !exists {
return nil, errors.New("service not found") return nil, fmt.Errorf("service not found %s/%s", namespace, svc.Name)
} }
var portSpec *corev1.ServicePort var portSpec *corev1.ServicePort

View file

@ -1498,6 +1498,44 @@ func TestLoadIngressRoutes(t *testing.T) {
}, },
}, },
}, },
{
desc: "Simple Ingress Route, with error page middleware",
paths: []string{"services.yml", "with_error_page.yml"},
expected: &dynamic.Configuration{
TLS: &dynamic.TLSConfiguration{},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{
"default/errorpage": {
Errors: &dynamic.ErrorPage{
Status: []string{"404", "500"},
Service: "default/errorpage-errorpage-service",
Query: "query",
},
},
},
Services: map[string]*dynamic.Service{
"default/errorpage-errorpage-service": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: true,
},
},
},
},
},
},
{ {
desc: "port selected by name (TODO)", desc: "port selected by name (TODO)",
}, },
@ -1506,6 +1544,9 @@ func TestLoadIngressRoutes(t *testing.T) {
for _, test := range testCases { for _, test := range testCases {
test := test test := test
if test.desc != "Simple Ingress Route, with error page middleware" {
continue
}
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()

View file

@ -28,7 +28,7 @@ type MiddlewareSpec struct {
Chain *Chain `json:"chain,omitempty"` Chain *Chain `json:"chain,omitempty"`
IPWhiteList *dynamic.IPWhiteList `json:"ipWhiteList,omitempty"` IPWhiteList *dynamic.IPWhiteList `json:"ipWhiteList,omitempty"`
Headers *dynamic.Headers `json:"headers,omitempty"` Headers *dynamic.Headers `json:"headers,omitempty"`
Errors *dynamic.ErrorPage `json:"errors,omitempty"` Errors *ErrorPage `json:"errors,omitempty"`
RateLimit *dynamic.RateLimit `json:"rateLimit,omitempty"` RateLimit *dynamic.RateLimit `json:"rateLimit,omitempty"`
RedirectRegex *dynamic.RedirectRegex `json:"redirectRegex,omitempty"` RedirectRegex *dynamic.RedirectRegex `json:"redirectRegex,omitempty"`
RedirectScheme *dynamic.RedirectScheme `json:"redirectScheme,omitempty"` RedirectScheme *dynamic.RedirectScheme `json:"redirectScheme,omitempty"`
@ -45,6 +45,15 @@ type MiddlewareSpec struct {
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
// ErrorPage holds the custom error page configuration.
type ErrorPage struct {
Status []string `json:"status,omitempty"`
Service Service `json:"service,omitempty"`
Query string `json:"query,omitempty"`
}
// +k8s:deepcopy-gen=true
// Chain holds a chain of middlewares // Chain holds a chain of middlewares
type Chain struct { type Chain struct {
Middlewares []MiddlewareRef `json:"middlewares,omitempty"` Middlewares []MiddlewareRef `json:"middlewares,omitempty"`

View file

@ -124,6 +124,28 @@ func (in *DigestAuth) DeepCopy() *DigestAuth {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ErrorPage) DeepCopyInto(out *ErrorPage) {
*out = *in
if in.Status != nil {
in, out := &in.Status, &out.Status
*out = make([]string, len(*in))
copy(*out, *in)
}
in.Service.DeepCopyInto(&out.Service)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ErrorPage.
func (in *ErrorPage) DeepCopy() *ErrorPage {
if in == nil {
return nil
}
out := new(ErrorPage)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) { func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) {
*out = *in *out = *in
@ -480,7 +502,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) {
} }
if in.Errors != nil { if in.Errors != nil {
in, out := &in.Errors, &out.Errors in, out := &in.Errors, &out.Errors
*out = new(dynamic.ErrorPage) *out = new(ErrorPage)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.RateLimit != nil { if in.RateLimit != nil {