k8s ErrorPage middleware now uses k8s service
This commit is contained in:
parent
34be181706
commit
fb8edd86d5
7 changed files with 130 additions and 8 deletions
|
@ -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.
|
||||||
|
|
15
pkg/provider/kubernetes/crd/fixtures/with_error_page.yml
Normal file
15
pkg/provider/kubernetes/crd/fixtures/with_error_page.yml
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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"`
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue