Support HTTPRoute redirect port and scheme
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
parent
27af1fb478
commit
3ca667a3d4
31 changed files with 1063 additions and 997 deletions
|
@ -204,12 +204,12 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
|
||||||
ksuite.SupportHTTPRouteSchemeRedirect,
|
ksuite.SupportHTTPRouteSchemeRedirect,
|
||||||
ksuite.SupportHTTPRouteHostRewrite,
|
ksuite.SupportHTTPRouteHostRewrite,
|
||||||
ksuite.SupportHTTPRoutePathRewrite,
|
ksuite.SupportHTTPRoutePathRewrite,
|
||||||
|
ksuite.SupportHTTPRoutePathRedirect,
|
||||||
),
|
),
|
||||||
ExemptFeatures: sets.New(
|
ExemptFeatures: sets.New(
|
||||||
ksuite.SupportHTTPRouteRequestTimeout,
|
ksuite.SupportHTTPRouteRequestTimeout,
|
||||||
ksuite.SupportHTTPRouteBackendTimeout,
|
ksuite.SupportHTTPRouteBackendTimeout,
|
||||||
ksuite.SupportHTTPRouteResponseHeaderModification,
|
ksuite.SupportHTTPRouteResponseHeaderModification,
|
||||||
ksuite.SupportHTTPRoutePathRedirect,
|
|
||||||
ksuite.SupportHTTPRouteRequestMirror,
|
ksuite.SupportHTTPRouteRequestMirror,
|
||||||
ksuite.SupportHTTPRouteRequestMultipleMirrors,
|
ksuite.SupportHTTPRouteRequestMultipleMirrors,
|
||||||
),
|
),
|
||||||
|
|
40
integration/testdata/rawdata-gateway.json
vendored
40
integration/testdata/rawdata-gateway.json
vendored
|
@ -30,27 +30,27 @@
|
||||||
"traefik"
|
"traefik"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-http-app-1-my-gateway-web-af4b9876d1fe36359e27@kubernetesgateway": {
|
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06@kubernetesgateway": {
|
||||||
"entryPoints": [
|
"entryPoints": [
|
||||||
"web"
|
"web"
|
||||||
],
|
],
|
||||||
"service": "default-http-app-1-my-gateway-web-af4b9876d1fe36359e27-wrr",
|
"service": "default-http-app-1-my-gateway-web-0-wrr",
|
||||||
"rule": "Host(`foo.com`) \u0026\u0026 (Path(`/bar`))",
|
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
||||||
"ruleSyntax": "v3",
|
"ruleSyntax": "v3",
|
||||||
"priority": 99997,
|
"priority": 100008,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-http-app-1-my-https-gateway-websecure-af4b9876d1fe36359e27@kubernetesgateway": {
|
"default-http-app-1-my-https-gateway-websecure-0-1c0cf64bde37d9d0df06@kubernetesgateway": {
|
||||||
"entryPoints": [
|
"entryPoints": [
|
||||||
"websecure"
|
"websecure"
|
||||||
],
|
],
|
||||||
"service": "default-http-app-1-my-https-gateway-websecure-af4b9876d1fe36359e27-wrr",
|
"service": "default-http-app-1-my-https-gateway-websecure-0-wrr",
|
||||||
"rule": "Host(`foo.com`) \u0026\u0026 (Path(`/bar`))",
|
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
||||||
"ruleSyntax": "v3",
|
"ruleSyntax": "v3",
|
||||||
"priority": 99997,
|
"priority": 100008,
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
@ -96,7 +96,7 @@
|
||||||
"dashboard@internal"
|
"dashboard@internal"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-http-app-1-my-gateway-web-af4b9876d1fe36359e27-wrr@kubernetesgateway": {
|
"default-http-app-1-my-gateway-web-0-wrr@kubernetesgateway": {
|
||||||
"weighted": {
|
"weighted": {
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
@ -107,10 +107,10 @@
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
"default-http-app-1-my-gateway-web-af4b9876d1fe36359e27@kubernetesgateway"
|
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06@kubernetesgateway"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-http-app-1-my-https-gateway-websecure-af4b9876d1fe36359e27-wrr@kubernetesgateway": {
|
"default-http-app-1-my-https-gateway-websecure-0-wrr@kubernetesgateway": {
|
||||||
"weighted": {
|
"weighted": {
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
@ -121,7 +121,7 @@
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
"default-http-app-1-my-https-gateway-websecure-af4b9876d1fe36359e27@kubernetesgateway"
|
"default-http-app-1-my-https-gateway-websecure-0-1c0cf64bde37d9d0df06@kubernetesgateway"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-whoami-80@kubernetesgateway": {
|
"default-whoami-80@kubernetesgateway": {
|
||||||
|
@ -150,11 +150,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tcpRouters": {
|
"tcpRouters": {
|
||||||
"default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb@kubernetesgateway": {
|
"default-tcp-app-1-my-tcp-gateway-footcp@kubernetesgateway": {
|
||||||
"entryPoints": [
|
"entryPoints": [
|
||||||
"footcp"
|
"footcp"
|
||||||
],
|
],
|
||||||
"service": "default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb-wrr-0",
|
"service": "default-tcp-app-1-my-tcp-gateway-footcp-wrr-0",
|
||||||
"rule": "HostSNI(`*`)",
|
"rule": "HostSNI(`*`)",
|
||||||
"ruleSyntax": "v3",
|
"ruleSyntax": "v3",
|
||||||
"priority": -1,
|
"priority": -1,
|
||||||
|
@ -163,11 +163,11 @@
|
||||||
"footcp"
|
"footcp"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb@kubernetesgateway": {
|
"default-tcp-app-1-my-tls-gateway-footlsterminate@kubernetesgateway": {
|
||||||
"entryPoints": [
|
"entryPoints": [
|
||||||
"footlsterminate"
|
"footlsterminate"
|
||||||
],
|
],
|
||||||
"service": "default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb-wrr-0",
|
"service": "default-tcp-app-1-my-tls-gateway-footlsterminate-wrr-0",
|
||||||
"rule": "HostSNI(`*`)",
|
"rule": "HostSNI(`*`)",
|
||||||
"ruleSyntax": "v3",
|
"ruleSyntax": "v3",
|
||||||
"priority": -1,
|
"priority": -1,
|
||||||
|
@ -197,7 +197,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tcpServices": {
|
"tcpServices": {
|
||||||
"default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb-wrr-0@kubernetesgateway": {
|
"default-tcp-app-1-my-tcp-gateway-footcp-wrr-0@kubernetesgateway": {
|
||||||
"weighted": {
|
"weighted": {
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
@ -208,10 +208,10 @@
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
"default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb@kubernetesgateway"
|
"default-tcp-app-1-my-tcp-gateway-footcp@kubernetesgateway"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb-wrr-0@kubernetesgateway": {
|
"default-tcp-app-1-my-tls-gateway-footlsterminate-wrr-0@kubernetesgateway": {
|
||||||
"weighted": {
|
"weighted": {
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
@ -222,7 +222,7 @@
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
"default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb@kubernetesgateway"
|
"default-tcp-app-1-my-tls-gateway-footlsterminate@kubernetesgateway"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26-wrr-0@kubernetesgateway": {
|
"default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26-wrr-0@kubernetesgateway": {
|
||||||
|
|
|
@ -696,7 +696,10 @@ type RequestHeaderModifier struct {
|
||||||
|
|
||||||
// RequestRedirect holds the request redirect middleware configuration.
|
// RequestRedirect holds the request redirect middleware configuration.
|
||||||
type RequestRedirect struct {
|
type RequestRedirect struct {
|
||||||
Regex string `json:"regex,omitempty"`
|
Scheme *string `json:"scheme,omitempty"`
|
||||||
Replacement string `json:"replacement,omitempty"`
|
Hostname *string `json:"hostname,omitempty"`
|
||||||
Permanent bool `json:"permanent,omitempty"`
|
Port *string `json:"port,omitempty"`
|
||||||
|
Path *string `json:"path,omitempty"`
|
||||||
|
PathPrefix *string `json:"pathPrefix,omitempty"`
|
||||||
|
StatusCode int `json:"statusCode,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -867,7 +867,7 @@ func (in *Middleware) DeepCopyInto(out *Middleware) {
|
||||||
if in.RequestRedirect != nil {
|
if in.RequestRedirect != nil {
|
||||||
in, out := &in.RequestRedirect, &out.RequestRedirect
|
in, out := &in.RequestRedirect, &out.RequestRedirect
|
||||||
*out = new(RequestRedirect)
|
*out = new(RequestRedirect)
|
||||||
**out = **in
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1115,6 +1115,31 @@ func (in *RequestHeaderModifier) DeepCopy() *RequestHeaderModifier {
|
||||||
// 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 *RequestRedirect) DeepCopyInto(out *RequestRedirect) {
|
func (in *RequestRedirect) DeepCopyInto(out *RequestRedirect) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.Scheme != nil {
|
||||||
|
in, out := &in.Scheme, &out.Scheme
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.Hostname != nil {
|
||||||
|
in, out := &in.Hostname, &out.Hostname
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.Port != nil {
|
||||||
|
in, out := &in.Port, &out.Port
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.Path != nil {
|
||||||
|
in, out := &in.Path, &out.Path
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.PathPrefix != nil {
|
||||||
|
in, out := &in.PathPrefix, &out.PathPrefix
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,133 +2,101 @@ package redirect
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"path"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||||
"github.com/vulcand/oxy/v2/utils"
|
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
schemeHTTP = "http"
|
typeName = "RequestRedirect"
|
||||||
schemeHTTPS = "https"
|
|
||||||
typeName = "RequestRedirect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var uriRegexp = regexp.MustCompile(`^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`)
|
type redirect struct {
|
||||||
|
name string
|
||||||
|
next http.Handler
|
||||||
|
scheme *string
|
||||||
|
hostname *string
|
||||||
|
port *string
|
||||||
|
path *string
|
||||||
|
pathPrefix *string
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
|
||||||
// NewRequestRedirect creates a redirect middleware.
|
// NewRequestRedirect creates a redirect middleware.
|
||||||
func NewRequestRedirect(ctx context.Context, next http.Handler, conf dynamic.RequestRedirect, name string) (http.Handler, error) {
|
func NewRequestRedirect(ctx context.Context, next http.Handler, conf dynamic.RequestRedirect, name string) (http.Handler, error) {
|
||||||
logger := middlewares.GetLogger(ctx, name, typeName)
|
logger := middlewares.GetLogger(ctx, name, typeName)
|
||||||
logger.Debug().Msg("Creating middleware")
|
logger.Debug().Msg("Creating middleware")
|
||||||
logger.Debug().Msgf("Setting up redirection from %s to %s", conf.Regex, conf.Replacement)
|
|
||||||
|
|
||||||
re, err := regexp.Compile(conf.Regex)
|
statusCode := conf.StatusCode
|
||||||
if err != nil {
|
if statusCode == 0 {
|
||||||
return nil, err
|
statusCode = http.StatusFound
|
||||||
|
}
|
||||||
|
if statusCode != http.StatusMovedPermanently && statusCode != http.StatusFound {
|
||||||
|
return nil, fmt.Errorf("unsupported status code: %d", statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &redirect{
|
return redirect{
|
||||||
regex: re,
|
name: name,
|
||||||
replacement: conf.Replacement,
|
next: next,
|
||||||
permanent: conf.Permanent,
|
scheme: conf.Scheme,
|
||||||
errHandler: utils.DefaultHandler,
|
hostname: conf.Hostname,
|
||||||
next: next,
|
port: conf.Port,
|
||||||
name: name,
|
path: conf.Path,
|
||||||
rawURL: rawURL,
|
pathPrefix: conf.PathPrefix,
|
||||||
|
statusCode: statusCode,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type redirect struct {
|
func (r redirect) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||||
next http.Handler
|
|
||||||
regex *regexp.Regexp
|
|
||||||
replacement string
|
|
||||||
permanent bool
|
|
||||||
errHandler utils.ErrorHandler
|
|
||||||
name string
|
|
||||||
rawURL func(*http.Request) string
|
|
||||||
}
|
|
||||||
|
|
||||||
func rawURL(req *http.Request) string {
|
|
||||||
scheme := schemeHTTP
|
|
||||||
host := req.Host
|
|
||||||
port := ""
|
|
||||||
uri := req.RequestURI
|
|
||||||
|
|
||||||
if match := uriRegexp.FindStringSubmatch(req.RequestURI); len(match) > 0 {
|
|
||||||
scheme = match[1]
|
|
||||||
|
|
||||||
if len(match[2]) > 0 {
|
|
||||||
host = match[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(match[3]) > 0 {
|
|
||||||
port = match[3]
|
|
||||||
}
|
|
||||||
|
|
||||||
uri = match[4]
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.TLS != nil {
|
|
||||||
scheme = schemeHTTPS
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *redirect) GetTracingInformation() (string, string, trace.SpanKind) {
|
|
||||||
return r.name, typeName, trace.SpanKindInternal
|
return r.name, typeName, trace.SpanKindInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (r redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
oldURL := r.rawURL(req)
|
redirectURL := *req.URL
|
||||||
|
redirectURL.Host = req.Host
|
||||||
|
|
||||||
// If the Regexp doesn't match, skip to the next handler.
|
if r.scheme != nil {
|
||||||
if !r.regex.MatchString(oldURL) {
|
redirectURL.Scheme = *r.scheme
|
||||||
r.next.ServeHTTP(rw, req)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply a rewrite regexp to the URL.
|
host := redirectURL.Hostname()
|
||||||
newURL := r.regex.ReplaceAllString(oldURL, r.replacement)
|
if r.hostname != nil {
|
||||||
|
host = *r.hostname
|
||||||
// Parse the rewritten URL and replace request URL with it.
|
|
||||||
parsedURL, err := url.Parse(newURL)
|
|
||||||
if err != nil {
|
|
||||||
r.errHandler.ServeHTTP(rw, req, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := &moveHandler{location: parsedURL, permanent: r.permanent}
|
port := redirectURL.Port()
|
||||||
handler.ServeHTTP(rw, req)
|
if r.port != nil {
|
||||||
}
|
port = *r.port
|
||||||
|
|
||||||
type moveHandler struct {
|
|
||||||
location *url.URL
|
|
||||||
permanent bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
rw.Header().Set("Location", m.location.String())
|
|
||||||
|
|
||||||
status := http.StatusFound
|
|
||||||
if req.Method != http.MethodGet {
|
|
||||||
status = http.StatusTemporaryRedirect
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.permanent {
|
if port != "" {
|
||||||
status = http.StatusMovedPermanently
|
host = net.JoinHostPort(host, port)
|
||||||
if req.Method != http.MethodGet {
|
}
|
||||||
status = http.StatusPermanentRedirect
|
redirectURL.Host = host
|
||||||
|
|
||||||
|
if r.path != nil && r.pathPrefix == nil {
|
||||||
|
redirectURL.Path = *r.path
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.path != nil && r.pathPrefix != nil {
|
||||||
|
redirectURL.Path = path.Join(*r.path, strings.TrimPrefix(req.URL.Path, *r.pathPrefix))
|
||||||
|
|
||||||
|
// add the trailing slash if needed, as path.Join removes trailing slashes.
|
||||||
|
if strings.HasSuffix(req.URL.Path, "/") && !strings.HasSuffix(redirectURL.Path, "/") {
|
||||||
|
redirectURL.Path += "/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rw.WriteHeader(status)
|
|
||||||
_, err := rw.Write([]byte(http.StatusText(status)))
|
rw.Header().Set("Location", redirectURL.String())
|
||||||
if err != nil {
|
|
||||||
|
rw.WriteHeader(r.statusCode)
|
||||||
|
if _, err := rw.Write([]byte(http.StatusText(r.statusCode))); err != nil {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRequestRedirectHandler(t *testing.T) {
|
func TestRequestRedirectHandler(t *testing.T) {
|
||||||
|
@ -24,49 +25,107 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
errorExpected bool
|
errorExpected bool
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
desc: "wrong status code",
|
||||||
|
config: dynamic.RequestRedirect{
|
||||||
|
Path: ptr.To("/baz"),
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
url: "http://foo.com:80/foo/bar",
|
||||||
|
errorExpected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace path",
|
||||||
|
config: dynamic.RequestRedirect{
|
||||||
|
Path: ptr.To("/baz"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com:80/foo/bar",
|
||||||
|
expectedURL: "http://foo.com:80/baz",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace path without trailing slash",
|
||||||
|
config: dynamic.RequestRedirect{
|
||||||
|
Path: ptr.To("/baz"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com:80/foo/bar/",
|
||||||
|
expectedURL: "http://foo.com:80/baz",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace path with trailing slash",
|
||||||
|
config: dynamic.RequestRedirect{
|
||||||
|
Path: ptr.To("/baz/"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com:80/foo/bar",
|
||||||
|
expectedURL: "http://foo.com:80/baz/",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "only hostname",
|
||||||
|
config: dynamic.RequestRedirect{
|
||||||
|
Hostname: ptr.To("bar.com"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com:8080/foo/",
|
||||||
|
expectedURL: "http://bar.com:8080/foo/",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace prefix path",
|
||||||
|
config: dynamic.RequestRedirect{
|
||||||
|
Path: ptr.To("/baz"),
|
||||||
|
PathPrefix: ptr.To("/foo"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com:80/foo/bar",
|
||||||
|
expectedURL: "http://foo.com:80/baz/bar",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace prefix path with trailing slash",
|
||||||
|
config: dynamic.RequestRedirect{
|
||||||
|
Path: ptr.To("/baz"),
|
||||||
|
PathPrefix: ptr.To("/foo"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com:80/foo/bar/",
|
||||||
|
expectedURL: "http://foo.com:80/baz/bar/",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace prefix path without slash prefix",
|
||||||
|
config: dynamic.RequestRedirect{
|
||||||
|
Path: ptr.To("baz"),
|
||||||
|
PathPrefix: ptr.To("/foo"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com:80/foo/bar",
|
||||||
|
expectedURL: "http://foo.com:80/baz/bar",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace prefix path without slash prefix",
|
||||||
|
config: dynamic.RequestRedirect{
|
||||||
|
Path: ptr.To("/baz"),
|
||||||
|
PathPrefix: ptr.To("/foo/"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com:80/foo/bar",
|
||||||
|
expectedURL: "http://foo.com:80/baz/bar",
|
||||||
|
expectedStatus: http.StatusFound,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "simple redirection",
|
desc: "simple redirection",
|
||||||
config: dynamic.RequestRedirect{
|
config: dynamic.RequestRedirect{
|
||||||
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
Scheme: ptr.To("https"),
|
||||||
Replacement: "https://${1}bar$2:443$4",
|
Hostname: ptr.To("foobar.com"),
|
||||||
|
Port: ptr.To("443"),
|
||||||
},
|
},
|
||||||
url: "http://foo.com:80",
|
url: "http://foo.com:80",
|
||||||
expectedURL: "https://foobar.com:443",
|
expectedURL: "https://foobar.com:443",
|
||||||
expectedStatus: http.StatusFound,
|
expectedStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
desc: "URL doesn't match regex",
|
|
||||||
config: dynamic.RequestRedirect{
|
|
||||||
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
|
||||||
Replacement: "https://${1}bar$2:443$4",
|
|
||||||
},
|
|
||||||
url: "http://bar.com:80",
|
|
||||||
expectedStatus: http.StatusOK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "invalid rewritten URL",
|
|
||||||
config: dynamic.RequestRedirect{
|
|
||||||
Regex: `^(.*)$`,
|
|
||||||
Replacement: "http://192.168.0.%31/",
|
|
||||||
},
|
|
||||||
url: "http://foo.com:80",
|
|
||||||
expectedStatus: http.StatusBadGateway,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "invalid regex",
|
|
||||||
config: dynamic.RequestRedirect{
|
|
||||||
Regex: `^(.*`,
|
|
||||||
Replacement: "$1",
|
|
||||||
},
|
|
||||||
url: "http://foo.com:80",
|
|
||||||
errorExpected: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTPS permanent",
|
desc: "HTTP to HTTPS permanent",
|
||||||
config: dynamic.RequestRedirect{
|
config: dynamic.RequestRedirect{
|
||||||
Regex: `^http://`,
|
Scheme: ptr.To("https"),
|
||||||
Replacement: "https://$1",
|
StatusCode: http.StatusMovedPermanently,
|
||||||
Permanent: true,
|
|
||||||
},
|
},
|
||||||
url: "http://foo",
|
url: "http://foo",
|
||||||
expectedURL: "https://foo",
|
expectedURL: "https://foo",
|
||||||
|
@ -75,9 +134,8 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "HTTPS to HTTP permanent",
|
desc: "HTTPS to HTTP permanent",
|
||||||
config: dynamic.RequestRedirect{
|
config: dynamic.RequestRedirect{
|
||||||
Regex: `https://foo`,
|
Scheme: ptr.To("http"),
|
||||||
Replacement: "http://foo",
|
StatusCode: http.StatusMovedPermanently,
|
||||||
Permanent: true,
|
|
||||||
},
|
},
|
||||||
secured: true,
|
secured: true,
|
||||||
url: "https://foo",
|
url: "https://foo",
|
||||||
|
@ -87,8 +145,8 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTPS",
|
desc: "HTTP to HTTPS",
|
||||||
config: dynamic.RequestRedirect{
|
config: dynamic.RequestRedirect{
|
||||||
Regex: `http://foo:80`,
|
Scheme: ptr.To("https"),
|
||||||
Replacement: "https://foo:443",
|
Port: ptr.To("443"),
|
||||||
},
|
},
|
||||||
url: "http://foo:80",
|
url: "http://foo:80",
|
||||||
expectedURL: "https://foo:443",
|
expectedURL: "https://foo:443",
|
||||||
|
@ -97,8 +155,8 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTPS, with X-Forwarded-Proto",
|
desc: "HTTP to HTTPS, with X-Forwarded-Proto",
|
||||||
config: dynamic.RequestRedirect{
|
config: dynamic.RequestRedirect{
|
||||||
Regex: `http://foo:80`,
|
Scheme: ptr.To("https"),
|
||||||
Replacement: "https://foo:443",
|
Port: ptr.To("443"),
|
||||||
},
|
},
|
||||||
url: "http://foo:80",
|
url: "http://foo:80",
|
||||||
headers: map[string]string{
|
headers: map[string]string{
|
||||||
|
@ -110,8 +168,8 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "HTTPS to HTTP",
|
desc: "HTTPS to HTTP",
|
||||||
config: dynamic.RequestRedirect{
|
config: dynamic.RequestRedirect{
|
||||||
Regex: `https://foo:443`,
|
Scheme: ptr.To("http"),
|
||||||
Replacement: "http://foo:80",
|
Port: ptr.To("80"),
|
||||||
},
|
},
|
||||||
secured: true,
|
secured: true,
|
||||||
url: "https://foo:443",
|
url: "https://foo:443",
|
||||||
|
@ -121,36 +179,13 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTP",
|
desc: "HTTP to HTTP",
|
||||||
config: dynamic.RequestRedirect{
|
config: dynamic.RequestRedirect{
|
||||||
Regex: `http://foo:80`,
|
Scheme: ptr.To("http"),
|
||||||
Replacement: "http://foo:88",
|
Port: ptr.To("88"),
|
||||||
},
|
},
|
||||||
url: "http://foo:80",
|
url: "http://foo:80",
|
||||||
expectedURL: "http://foo:88",
|
expectedURL: "http://foo:88",
|
||||||
expectedStatus: http.StatusFound,
|
expectedStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
desc: "HTTP to HTTP POST",
|
|
||||||
config: dynamic.RequestRedirect{
|
|
||||||
Regex: `^http://`,
|
|
||||||
Replacement: "https://$1",
|
|
||||||
},
|
|
||||||
url: "http://foo",
|
|
||||||
method: http.MethodPost,
|
|
||||||
expectedURL: "https://foo",
|
|
||||||
expectedStatus: http.StatusTemporaryRedirect,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "HTTP to HTTP POST permanent",
|
|
||||||
config: dynamic.RequestRedirect{
|
|
||||||
Regex: `^http://`,
|
|
||||||
Replacement: "https://$1",
|
|
||||||
Permanent: true,
|
|
||||||
},
|
|
||||||
url: "http://foo",
|
|
||||||
method: http.MethodPost,
|
|
||||||
expectedURL: "https://foo",
|
|
||||||
expectedStatus: http.StatusPermanentRedirect,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
@ -188,7 +223,7 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, test.expectedStatus, recorder.Code)
|
assert.Equal(t, test.expectedStatus, recorder.Code)
|
||||||
switch test.expectedStatus {
|
switch test.expectedStatus {
|
||||||
case http.StatusMovedPermanently, http.StatusFound, http.StatusTemporaryRedirect, http.StatusPermanentRedirect:
|
case http.StatusMovedPermanently, http.StatusFound:
|
||||||
location, err := recorder.Result().Location()
|
location, err := recorder.Result().Location()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,11 @@ spec:
|
||||||
hostnames:
|
hostnames:
|
||||||
- "example.org"
|
- "example.org"
|
||||||
rules:
|
rules:
|
||||||
- filters:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
filters:
|
||||||
- type: RequestRedirect
|
- type: RequestRedirect
|
||||||
requestRedirect:
|
requestRedirect:
|
||||||
scheme: https
|
scheme: https
|
||||||
|
|
|
@ -39,7 +39,11 @@ spec:
|
||||||
hostnames:
|
hostnames:
|
||||||
- "example.org"
|
- "example.org"
|
||||||
rules:
|
rules:
|
||||||
- filters:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
filters:
|
||||||
- type: RequestRedirect
|
- type: RequestRedirect
|
||||||
requestRedirect:
|
requestRedirect:
|
||||||
hostname: example.com
|
hostname: example.com
|
||||||
|
|
|
@ -39,7 +39,11 @@ spec:
|
||||||
hostnames:
|
hostnames:
|
||||||
- "example.org"
|
- "example.org"
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -37,7 +37,11 @@ spec:
|
||||||
- "foo.com"
|
- "foo.com"
|
||||||
- "bar.com"
|
- "bar.com"
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -107,7 +107,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
@ -131,7 +135,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoamitcp
|
- name: whoamitcp
|
||||||
port: 9000
|
port: 9000
|
||||||
weight: 1
|
weight: 1
|
||||||
|
@ -151,7 +159,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoamitcp
|
- name: whoamitcp
|
||||||
port: 9000
|
port: 9000
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -33,7 +33,11 @@ metadata:
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -33,7 +33,11 @@ metadata:
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -89,7 +89,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -33,7 +33,11 @@ metadata:
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -119,7 +119,11 @@ metadata:
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
@ -134,7 +138,11 @@ metadata:
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -107,7 +107,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
@ -177,7 +181,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami-bar
|
- name: whoami-bar
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -107,7 +107,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
@ -177,7 +181,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami-bar
|
- name: whoami-bar
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -130,7 +130,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
@ -200,7 +204,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami-bar
|
- name: whoami-bar
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -53,7 +53,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami-bar
|
- name: whoami-bar
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -38,7 +38,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami-bar
|
- name: whoami-bar
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -53,7 +53,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami-bar
|
- name: whoami-bar
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -54,7 +54,11 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
group: gateway.networking.k8s.io
|
group: gateway.networking.k8s.io
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami-bar
|
- name: whoami-bar
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -37,7 +37,11 @@ spec:
|
||||||
- "foo.com"
|
- "foo.com"
|
||||||
- "*.bar.com"
|
- "*.bar.com"
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -37,7 +37,11 @@ spec:
|
||||||
- "foo.com"
|
- "foo.com"
|
||||||
- "*.foo.com"
|
- "*.foo.com"
|
||||||
rules:
|
rules:
|
||||||
- backendRefs:
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
- name: whoami
|
- name: whoami
|
||||||
port: 80
|
port: 80
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|
|
@ -20,8 +20,8 @@ import (
|
||||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Provider) loadHTTPRoutes(ctx context.Context, client Client, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
func (p *Provider) loadHTTPRoutes(ctx context.Context, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
||||||
routes, err := client.ListHTTPRoutes()
|
routes, err := p.client.ListHTTPRoutes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Ctx(ctx).Error().Err(err).Msg("Unable to list HTTPRoutes")
|
log.Ctx(ctx).Error().Err(err).Msg("Unable to list HTTPRoutes")
|
||||||
return
|
return
|
||||||
|
@ -74,7 +74,7 @@ func (p *Provider) loadHTTPRoutes(ctx context.Context, client Client, gatewayLis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
routeConf, resolveRefCondition := p.loadHTTPRoute(logger.WithContext(ctx), client, listener, route, hostnames)
|
routeConf, resolveRefCondition := p.loadHTTPRoute(logger.WithContext(ctx), listener, route, hostnames)
|
||||||
if accepted && listener.Attached {
|
if accepted && listener.Attached {
|
||||||
mergeHTTPConfiguration(routeConf, conf)
|
mergeHTTPConfiguration(routeConf, conf)
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ func (p *Provider) loadHTTPRoutes(ctx context.Context, client Client, gatewayLis
|
||||||
Parents: parentStatuses,
|
Parents: parentStatuses,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := client.UpdateHTTPRouteStatus(ctx, ktypes.NamespacedName{Namespace: route.Namespace, Name: route.Name}, status); err != nil {
|
if err := p.client.UpdateHTTPRouteStatus(ctx, ktypes.NamespacedName{Namespace: route.Namespace, Name: route.Name}, status); err != nil {
|
||||||
logger.Error().
|
logger.Error().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Unable to update HTTPRoute status")
|
Msg("Unable to update HTTPRoute status")
|
||||||
|
@ -98,8 +98,8 @@ func (p *Provider) loadHTTPRoutes(ctx context.Context, client Client, gatewayLis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) loadHTTPRoute(ctx context.Context, client Client, listener gatewayListener, route *gatev1.HTTPRoute, hostnames []gatev1.Hostname) (*dynamic.Configuration, metav1.Condition) {
|
func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener, route *gatev1.HTTPRoute, hostnames []gatev1.Hostname) (*dynamic.Configuration, metav1.Condition) {
|
||||||
routeConf := &dynamic.Configuration{
|
conf := &dynamic.Configuration{
|
||||||
HTTP: &dynamic.HTTPConfiguration{
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
Routers: make(map[string]*dynamic.Router),
|
Routers: make(map[string]*dynamic.Router),
|
||||||
Middlewares: make(map[string]*dynamic.Middleware),
|
Middlewares: make(map[string]*dynamic.Middleware),
|
||||||
|
@ -108,7 +108,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, client Client, listener ga
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
routeCondition := metav1.Condition{
|
condition := metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionTrue,
|
Status: metav1.ConditionTrue,
|
||||||
ObservedGeneration: route.Generation,
|
ObservedGeneration: route.Generation,
|
||||||
|
@ -116,95 +116,101 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, client Client, listener ga
|
||||||
Reason: string(gatev1.RouteConditionResolvedRefs),
|
Reason: string(gatev1.RouteConditionResolvedRefs),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, routeRule := range route.Spec.Rules {
|
errWrr := dynamic.WeightedRoundRobin{
|
||||||
rule, priority := buildRouterRule(hostnames, routeRule.Matches)
|
Services: []dynamic.WRRService{
|
||||||
router := dynamic.Router{
|
{
|
||||||
RuleSyntax: "v3",
|
|
||||||
Rule: rule,
|
|
||||||
Priority: priority,
|
|
||||||
EntryPoints: []string{listener.EPName},
|
|
||||||
}
|
|
||||||
if listener.Protocol == gatev1.HTTPSProtocolType {
|
|
||||||
router.TLS = &dynamic.RouterTLSConfig{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
|
||||||
routerName := route.Name + "-" + listener.GWName + "-" + listener.EPName
|
|
||||||
routerKey := makeRouterKey(router.Rule, makeID(route.Namespace, routerName))
|
|
||||||
|
|
||||||
var wrr dynamic.WeightedRoundRobin
|
|
||||||
wrrName := provider.Normalize(routerKey + "-wrr")
|
|
||||||
|
|
||||||
middlewares, err := p.loadMiddlewares(route.Namespace, routerKey, routeRule.Filters)
|
|
||||||
if err != nil {
|
|
||||||
log.Ctx(ctx).Error().
|
|
||||||
Err(err).
|
|
||||||
Msg("Unable to load HTTPRoute filters")
|
|
||||||
|
|
||||||
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
|
||||||
Name: "invalid-httproute-filter",
|
Name: "invalid-httproute-filter",
|
||||||
Status: ptr.To(500),
|
Status: ptr.To(500),
|
||||||
Weight: ptr.To(1),
|
Weight: ptr.To(1),
|
||||||
})
|
},
|
||||||
|
},
|
||||||
routeConf.HTTP.Services[wrrName] = &dynamic.Service{Weighted: &wrr}
|
|
||||||
router.Service = wrrName
|
|
||||||
} else {
|
|
||||||
for name, middleware := range middlewares {
|
|
||||||
// If the middleware config is nil in the return of the loadMiddlewares function,
|
|
||||||
// it means that we just need a reference to that middleware.
|
|
||||||
if middleware != nil {
|
|
||||||
routeConf.HTTP.Middlewares[name] = middleware
|
|
||||||
}
|
|
||||||
|
|
||||||
router.Middlewares = append(router.Middlewares, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Traefik internal service can be used only if there is only one BackendRef service reference.
|
|
||||||
if len(routeRule.BackendRefs) == 1 && isInternalService(routeRule.BackendRefs[0].BackendRef) {
|
|
||||||
router.Service = string(routeRule.BackendRefs[0].Name)
|
|
||||||
} else {
|
|
||||||
for _, backendRef := range routeRule.BackendRefs {
|
|
||||||
name, svc, errCondition := p.loadHTTPService(client, route, backendRef)
|
|
||||||
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
|
|
||||||
if errCondition != nil {
|
|
||||||
routeCondition = *errCondition
|
|
||||||
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
|
||||||
Name: name,
|
|
||||||
Status: ptr.To(500),
|
|
||||||
Weight: weight,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if svc != nil {
|
|
||||||
routeConf.HTTP.Services[name] = svc
|
|
||||||
}
|
|
||||||
|
|
||||||
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
|
||||||
Name: name,
|
|
||||||
Weight: weight,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
routeConf.HTTP.Services[wrrName] = &dynamic.Service{Weighted: &wrr}
|
|
||||||
router.Service = wrrName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rt := &router
|
|
||||||
p.applyRouterTransform(ctx, rt, route)
|
|
||||||
|
|
||||||
routerKey = provider.Normalize(routerKey)
|
|
||||||
routeConf.HTTP.Routers[routerKey] = rt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return routeConf, routeCondition
|
for ri, routeRule := range route.Spec.Rules {
|
||||||
|
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
||||||
|
routeKey := provider.Normalize(fmt.Sprintf("%s-%s-%s-%s-%d", route.Namespace, route.Name, listener.GWName, listener.EPName, ri))
|
||||||
|
|
||||||
|
for _, match := range routeRule.Matches {
|
||||||
|
rule, priority := buildMatchRule(hostnames, match)
|
||||||
|
router := dynamic.Router{
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
Rule: rule,
|
||||||
|
Priority: priority + len(route.Spec.Rules) - ri,
|
||||||
|
EntryPoints: []string{listener.EPName},
|
||||||
|
}
|
||||||
|
if listener.Protocol == gatev1.HTTPSProtocolType {
|
||||||
|
router.TLS = &dynamic.RouterTLSConfig{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
routerName := makeRouterName(rule, routeKey)
|
||||||
|
router.Middlewares, err = p.loadMiddlewares(conf, route.Namespace, routerName, routeRule.Filters, match.Path)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
log.Ctx(ctx).Error().Err(err).Msg("Unable to load HTTPRoute filters")
|
||||||
|
|
||||||
|
errWrrName := routerName + "-err-wrr"
|
||||||
|
conf.HTTP.Services[errWrrName] = &dynamic.Service{Weighted: &errWrr}
|
||||||
|
router.Service = errWrrName
|
||||||
|
|
||||||
|
case len(routeRule.BackendRefs) == 1 && isInternalService(routeRule.BackendRefs[0].BackendRef):
|
||||||
|
router.Service = string(routeRule.BackendRefs[0].Name)
|
||||||
|
|
||||||
|
default:
|
||||||
|
var serviceCondition *metav1.Condition
|
||||||
|
router.Service, serviceCondition = p.loadService(conf, routeKey, routeRule, route)
|
||||||
|
if serviceCondition != nil {
|
||||||
|
condition = *serviceCondition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.applyRouterTransform(ctx, &router, route)
|
||||||
|
|
||||||
|
conf.HTTP.Routers[routerName] = &router
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf, condition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) loadService(conf *dynamic.Configuration, routeKey string, routeRule gatev1.HTTPRouteRule, route *gatev1.HTTPRoute) (string, *metav1.Condition) {
|
||||||
|
name := routeKey + "-wrr"
|
||||||
|
if _, ok := conf.HTTP.Services[name]; ok {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrr dynamic.WeightedRoundRobin
|
||||||
|
var condition *metav1.Condition
|
||||||
|
for _, backendRef := range routeRule.BackendRefs {
|
||||||
|
svcName, svc, errCondition := p.loadHTTPService(route, backendRef)
|
||||||
|
weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1)))
|
||||||
|
if errCondition != nil {
|
||||||
|
condition = errCondition
|
||||||
|
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
||||||
|
Name: svcName,
|
||||||
|
Status: ptr.To(500),
|
||||||
|
Weight: weight,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc != nil {
|
||||||
|
conf.HTTP.Services[svcName] = svc
|
||||||
|
}
|
||||||
|
|
||||||
|
wrr.Services = append(wrr.Services, dynamic.WRRService{
|
||||||
|
Name: svcName,
|
||||||
|
Weight: weight,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.HTTP.Services[name] = &dynamic.Service{Weighted: &wrr}
|
||||||
|
return name, condition
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadHTTPService returns a dynamic.Service config corresponding to the given gatev1.HTTPBackendRef.
|
// loadHTTPService returns a dynamic.Service config corresponding to the given gatev1.HTTPBackendRef.
|
||||||
// Note that the returned dynamic.Service config can be nil (for cross-provider, internal services, and backendFunc).
|
// Note that the returned dynamic.Service config can be nil (for cross-provider, internal services, and backendFunc).
|
||||||
func (p *Provider) loadHTTPService(client Client, route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, *metav1.Condition) {
|
func (p *Provider) loadHTTPService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, *metav1.Condition) {
|
||||||
kind := ptr.Deref(backendRef.Kind, "Service")
|
kind := ptr.Deref(backendRef.Kind, "Service")
|
||||||
|
|
||||||
group := groupCore
|
group := groupCore
|
||||||
|
@ -217,9 +223,9 @@ func (p *Provider) loadHTTPService(client Client, route *gatev1.HTTPRoute, backe
|
||||||
namespace = string(*backendRef.Namespace)
|
namespace = string(*backendRef.Namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceName := provider.Normalize(makeID(namespace, string(backendRef.Name)))
|
serviceName := provider.Normalize(namespace + "-" + string(backendRef.Name))
|
||||||
|
|
||||||
if err := isReferenceGranted(client, groupGateway, kindHTTPRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {
|
if err := p.isReferenceGranted(groupGateway, kindHTTPRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil {
|
||||||
return serviceName, nil, &metav1.Condition{
|
return serviceName, nil, &metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
|
@ -261,7 +267,7 @@ func (p *Provider) loadHTTPService(client Client, route *gatev1.HTTPRoute, backe
|
||||||
portStr := strconv.FormatInt(int64(port), 10)
|
portStr := strconv.FormatInt(int64(port), 10)
|
||||||
serviceName = provider.Normalize(serviceName + "-" + portStr)
|
serviceName = provider.Normalize(serviceName + "-" + portStr)
|
||||||
|
|
||||||
lb, err := loadHTTPServers(client, namespace, backendRef)
|
lb, err := p.loadHTTPServers(namespace, backendRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return serviceName, nil, &metav1.Condition{
|
return serviceName, nil, &metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
|
@ -294,18 +300,17 @@ func (p *Provider) loadHTTPBackendRef(namespace string, backendRef gatev1.HTTPBa
|
||||||
return backendFunc(string(backendRef.Name), namespace)
|
return backendFunc(string(backendRef.Name), namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) loadMiddlewares(namespace, prefix string, filters []gatev1.HTTPRouteFilter) (map[string]*dynamic.Middleware, error) {
|
func (p *Provider) loadMiddlewares(conf *dynamic.Configuration, namespace, routerName string, filters []gatev1.HTTPRouteFilter, pathMatch *gatev1.HTTPPathMatch) ([]string, error) {
|
||||||
middlewares := make(map[string]*dynamic.Middleware)
|
middlewares := make(map[string]*dynamic.Middleware)
|
||||||
|
|
||||||
for i, filter := range filters {
|
for i, filter := range filters {
|
||||||
switch filter.Type {
|
switch filter.Type {
|
||||||
case gatev1.HTTPRouteFilterRequestRedirect:
|
case gatev1.HTTPRouteFilterRequestRedirect:
|
||||||
middlewareName := provider.Normalize(fmt.Sprintf("%s-%s-%d", prefix, strings.ToLower(string(filter.Type)), i))
|
name := fmt.Sprintf("%s-%s-%d", routerName, strings.ToLower(string(filter.Type)), i)
|
||||||
middlewares[middlewareName] = createRedirectMiddleware(filter.RequestRedirect)
|
middlewares[name] = createRequestRedirect(filter.RequestRedirect, pathMatch)
|
||||||
|
|
||||||
case gatev1.HTTPRouteFilterRequestHeaderModifier:
|
case gatev1.HTTPRouteFilterRequestHeaderModifier:
|
||||||
middlewareName := provider.Normalize(fmt.Sprintf("%s-%s-%d", prefix, strings.ToLower(string(filter.Type)), i))
|
name := fmt.Sprintf("%s-%s-%d", routerName, strings.ToLower(string(filter.Type)), i)
|
||||||
middlewares[middlewareName] = createRequestHeaderModifier(filter.RequestHeaderModifier)
|
middlewares[name] = createRequestHeaderModifier(filter.RequestHeaderModifier)
|
||||||
|
|
||||||
case gatev1.HTTPRouteFilterExtensionRef:
|
case gatev1.HTTPRouteFilterExtensionRef:
|
||||||
name, middleware, err := p.loadHTTPRouteFilterExtensionRef(namespace, filter.ExtensionRef)
|
name, middleware, err := p.loadHTTPRouteFilterExtensionRef(namespace, filter.ExtensionRef)
|
||||||
|
@ -324,7 +329,16 @@ func (p *Provider) loadMiddlewares(namespace, prefix string, filters []gatev1.HT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return middlewares, nil
|
var middlewareNames []string
|
||||||
|
for name, middleware := range middlewares {
|
||||||
|
if middleware != nil {
|
||||||
|
conf.HTTP.Middlewares[name] = middleware
|
||||||
|
}
|
||||||
|
|
||||||
|
middlewareNames = append(middlewareNames, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return middlewareNames, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRef *gatev1.LocalObjectReference) (string, *dynamic.Middleware, error) {
|
func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRef *gatev1.LocalObjectReference) (string, *dynamic.Middleware, error) {
|
||||||
|
@ -343,9 +357,8 @@ func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRe
|
||||||
return filterFunc(string(extensionRef.Name), namespace)
|
return filterFunc(string(extensionRef.Name), namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO support cross namespace through ReferencePolicy.
|
func (p *Provider) loadHTTPServers(namespace string, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, error) {
|
||||||
func loadHTTPServers(client Client, namespace string, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, error) {
|
service, exists, err := p.client.GetService(namespace, string(backendRef.Name))
|
||||||
service, exists, err := client.GetService(namespace, string(backendRef.Name))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("getting service: %w", err)
|
return nil, fmt.Errorf("getting service: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -367,7 +380,7 @@ func loadHTTPServers(client Client, namespace string, backendRef gatev1.HTTPBack
|
||||||
return nil, errors.New("service port not found")
|
return nil, errors.New("service port not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, endpointsExists, err := client.GetEndpoints(namespace, string(backendRef.Name))
|
endpoints, endpointsExists, err := p.client.GetEndpoints(namespace, string(backendRef.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("getting endpoints: %w", err)
|
return nil, fmt.Errorf("getting endpoints: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -440,7 +453,7 @@ func buildHostRule(hostnames []gatev1.Hostname) (string, int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildRouterRule builds the route rule and computes its priority.
|
// buildMatchRule builds the route rule and computes its priority.
|
||||||
// The current priority computing is rather naive but aims to fulfill Conformance tests suite requirement.
|
// The current priority computing is rather naive but aims to fulfill Conformance tests suite requirement.
|
||||||
// The priority is computed to match the following precedence order:
|
// The priority is computed to match the following precedence order:
|
||||||
//
|
//
|
||||||
|
@ -451,60 +464,34 @@ func buildHostRule(hostnames []gatev1.Hostname) (string, int) {
|
||||||
// * Largest number of query param matches. (not implemented)
|
// * Largest number of query param matches. (not implemented)
|
||||||
//
|
//
|
||||||
// In case of multiple matches for a route, the maximum priority among all matches is retain.
|
// In case of multiple matches for a route, the maximum priority among all matches is retain.
|
||||||
func buildRouterRule(hostnames []gatev1.Hostname, routeMatches []gatev1.HTTPRouteMatch) (string, int) {
|
func buildMatchRule(hostnames []gatev1.Hostname, match gatev1.HTTPRouteMatch) (string, int) {
|
||||||
var matchesRules []string
|
path := ptr.Deref(match.Path, gatev1.HTTPPathMatch{
|
||||||
var maxPriority int
|
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||||
|
Value: ptr.To("/"),
|
||||||
|
})
|
||||||
|
|
||||||
for _, match := range routeMatches {
|
var priority int
|
||||||
path := ptr.Deref(match.Path, gatev1.HTTPPathMatch{
|
var matchRules []string
|
||||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
|
||||||
Value: ptr.To("/"),
|
|
||||||
})
|
|
||||||
|
|
||||||
var priority int
|
pathRule, pathPriority := buildPathRule(path)
|
||||||
var matchRules []string
|
matchRules = append(matchRules, pathRule)
|
||||||
|
priority += pathPriority
|
||||||
|
|
||||||
pathRule, pathPriority := buildPathRule(path)
|
headerRules, headersPriority := buildHeaderRules(match.Headers)
|
||||||
matchRules = append(matchRules, pathRule)
|
matchRules = append(matchRules, headerRules...)
|
||||||
priority += pathPriority
|
priority += headersPriority
|
||||||
|
|
||||||
headerRules, headersPriority := buildHeaderRules(match.Headers)
|
matchRulesStr := strings.Join(matchRules, " && ")
|
||||||
matchRules = append(matchRules, headerRules...)
|
|
||||||
priority += headersPriority
|
|
||||||
|
|
||||||
matchesRules = append(matchesRules, strings.Join(matchRules, " && "))
|
|
||||||
|
|
||||||
if priority > maxPriority {
|
|
||||||
maxPriority = priority
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hostRule, hostPriority := buildHostRule(hostnames)
|
hostRule, hostPriority := buildHostRule(hostnames)
|
||||||
|
|
||||||
matchesRulesStr := strings.Join(matchesRules, " || ")
|
|
||||||
|
|
||||||
if hostRule == "" && matchesRulesStr == "" {
|
|
||||||
return "PathPrefix(`/`)", 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if hostRule != "" && matchesRulesStr == "" {
|
|
||||||
return hostRule, hostPriority
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enforce that, at the same priority,
|
|
||||||
// the route with fewer matches (more specific) matches first.
|
|
||||||
maxPriority -= len(matchesRules) * 10
|
|
||||||
if maxPriority < 1 {
|
|
||||||
maxPriority = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if hostRule == "" {
|
if hostRule == "" {
|
||||||
return matchesRulesStr, maxPriority
|
return matchRulesStr, priority
|
||||||
}
|
}
|
||||||
|
|
||||||
// A route with a host should match over the same route with no host.
|
// A route with a host should match over the same route with no host.
|
||||||
maxPriority += hostPriority
|
priority += hostPriority
|
||||||
return hostRule + " && " + "(" + matchesRulesStr + ")", maxPriority
|
return hostRule + " && " + matchRulesStr, priority
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPathRule(pathMatch gatev1.HTTPPathMatch) (string, int) {
|
func buildPathRule(pathMatch gatev1.HTTPPathMatch) (string, int) {
|
||||||
|
@ -573,29 +560,41 @@ func createRequestHeaderModifier(filter *gatev1.HTTPHeaderFilter) *dynamic.Middl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createRedirectMiddleware(filter *gatev1.HTTPRequestRedirectFilter) *dynamic.Middleware {
|
func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch *gatev1.HTTPPathMatch) *dynamic.Middleware {
|
||||||
filterScheme := ptr.Deref(filter.Scheme, "${scheme}")
|
var hostname *string
|
||||||
|
if filter.Hostname != nil {
|
||||||
|
hostname = ptr.To(string(*filter.Hostname))
|
||||||
|
}
|
||||||
|
|
||||||
port := "${port}"
|
var port *string
|
||||||
|
filterScheme := ptr.Deref(filter.Scheme, "")
|
||||||
if filterScheme == "http" || filterScheme == "https" {
|
if filterScheme == "http" || filterScheme == "https" {
|
||||||
port = ""
|
port = ptr.To("")
|
||||||
}
|
}
|
||||||
if filter.Port != nil {
|
if filter.Port != nil {
|
||||||
port = fmt.Sprintf(":%d", *filter.Port)
|
port = ptr.To(fmt.Sprintf("%d", *filter.Port))
|
||||||
}
|
}
|
||||||
|
|
||||||
statusCode := ptr.Deref(filter.StatusCode, http.StatusFound)
|
var path *string
|
||||||
|
var pathPrefix *string
|
||||||
hostname := "${hostname}"
|
if filter.Path != nil {
|
||||||
if filter.Hostname != nil && *filter.Hostname != "" {
|
switch filter.Path.Type {
|
||||||
hostname = string(*filter.Hostname)
|
case gatev1.FullPathHTTPPathModifier:
|
||||||
|
path = filter.Path.ReplaceFullPath
|
||||||
|
case gatev1.PrefixMatchHTTPPathModifier:
|
||||||
|
path = filter.Path.ReplacePrefixMatch
|
||||||
|
pathPrefix = pathMatch.Value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &dynamic.Middleware{
|
return &dynamic.Middleware{
|
||||||
RequestRedirect: &dynamic.RequestRedirect{
|
RequestRedirect: &dynamic.RequestRedirect{
|
||||||
Regex: `^(?P<scheme>[a-z]+):\/\/(?P<userinfo>.+@)?(?P<hostname>\[[\w:\.]+\]|[\w\._-]+)(?P<port>:\d+)?\/(?P<path>.*)`,
|
Scheme: filter.Scheme,
|
||||||
Replacement: fmt.Sprintf("%s://${userinfo}%s%s/${path}", filterScheme, hostname, port),
|
Hostname: hostname,
|
||||||
Permanent: statusCode == http.StatusMovedPermanently,
|
Port: port,
|
||||||
|
Path: path,
|
||||||
|
PathPrefix: pathPrefix,
|
||||||
|
StatusCode: ptr.Deref(filter.StatusCode, http.StatusFound),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,10 +69,10 @@ func Test_buildHostRule(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_buildRouterRule(t *testing.T) {
|
func Test_buildMatchRule(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
routeMatches []gatev1.HTTPRouteMatch
|
routeMatch gatev1.HTTPRouteMatch
|
||||||
hostnames []gatev1.Hostname
|
hostnames []gatev1.Hostname
|
||||||
expectedRule string
|
expectedRule string
|
||||||
expectedPriority int
|
expectedPriority int
|
||||||
|
@ -84,187 +84,112 @@ func Test_buildRouterRule(t *testing.T) {
|
||||||
expectedPriority: 1,
|
expectedPriority: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "One Host rule without matches",
|
desc: "One Host rule without match",
|
||||||
hostnames: []gatev1.Hostname{"foo.com"},
|
hostnames: []gatev1.Hostname{"foo.com"},
|
||||||
expectedRule: "Host(`foo.com`)",
|
expectedRule: "Host(`foo.com`) && PathPrefix(`/`)",
|
||||||
expectedPriority: 7,
|
expectedPriority: 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "One HTTPRouteMatch with nil HTTPHeaderMatch",
|
desc: "One HTTPRouteMatch with nil HTTPHeaderMatch",
|
||||||
routeMatches: []gatev1.HTTPRouteMatch{
|
routeMatch: gatev1.HTTPRouteMatch{
|
||||||
{
|
Path: ptr.To(gatev1.HTTPPathMatch{
|
||||||
Path: ptr.To(gatev1.HTTPPathMatch{
|
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
Value: ptr.To("/"),
|
||||||
Value: ptr.To("/"),
|
}),
|
||||||
}),
|
Headers: nil,
|
||||||
Headers: nil,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
expectedRule: "PathPrefix(`/`)",
|
expectedRule: "PathPrefix(`/`)",
|
||||||
expectedPriority: 1,
|
expectedPriority: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "One HTTPRouteMatch with nil HTTPHeaderMatch Type",
|
desc: "One HTTPRouteMatch with nil HTTPHeaderMatch Type",
|
||||||
routeMatches: []gatev1.HTTPRouteMatch{
|
routeMatch: gatev1.HTTPRouteMatch{
|
||||||
{
|
Path: ptr.To(gatev1.HTTPPathMatch{
|
||||||
Path: ptr.To(gatev1.HTTPPathMatch{
|
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
Value: ptr.To("/"),
|
||||||
Value: ptr.To("/"),
|
}),
|
||||||
}),
|
Headers: []gatev1.HTTPHeaderMatch{
|
||||||
Headers: []gatev1.HTTPHeaderMatch{
|
{Name: "foo", Value: "bar"},
|
||||||
{Name: "foo", Value: "bar"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedRule: "PathPrefix(`/`) && Header(`foo`,`bar`)",
|
expectedRule: "PathPrefix(`/`) && Header(`foo`,`bar`)",
|
||||||
expectedPriority: 91,
|
expectedPriority: 101,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "One HTTPRouteMatch with nil HTTPPathMatch",
|
desc: "One HTTPRouteMatch with nil HTTPPathMatch",
|
||||||
routeMatches: []gatev1.HTTPRouteMatch{
|
routeMatch: gatev1.HTTPRouteMatch{Path: nil},
|
||||||
{Path: nil},
|
|
||||||
},
|
|
||||||
expectedRule: "PathPrefix(`/`)",
|
expectedRule: "PathPrefix(`/`)",
|
||||||
expectedPriority: 1,
|
expectedPriority: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "One HTTPRouteMatch with nil HTTPPathMatch Type",
|
desc: "One HTTPRouteMatch with nil HTTPPathMatch Type",
|
||||||
routeMatches: []gatev1.HTTPRouteMatch{
|
routeMatch: gatev1.HTTPRouteMatch{
|
||||||
{
|
Path: &gatev1.HTTPPathMatch{
|
||||||
Path: &gatev1.HTTPPathMatch{
|
Type: nil,
|
||||||
Type: nil,
|
Value: ptr.To("/foo/"),
|
||||||
Value: ptr.To("/foo/"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedRule: "(Path(`/foo`) || PathPrefix(`/foo/`))",
|
expectedRule: "(Path(`/foo`) || PathPrefix(`/foo/`))",
|
||||||
expectedPriority: 10490,
|
expectedPriority: 10500,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "One HTTPRouteMatch with nil HTTPPathMatch Values",
|
desc: "One HTTPRouteMatch with nil HTTPPathMatch Values",
|
||||||
routeMatches: []gatev1.HTTPRouteMatch{
|
routeMatch: gatev1.HTTPRouteMatch{
|
||||||
{
|
Path: &gatev1.HTTPPathMatch{
|
||||||
Path: &gatev1.HTTPPathMatch{
|
Type: ptr.To(gatev1.PathMatchExact),
|
||||||
Type: ptr.To(gatev1.PathMatchExact),
|
Value: nil,
|
||||||
Value: nil,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedRule: "Path(`/`)",
|
expectedRule: "Path(`/`)",
|
||||||
expectedPriority: 99990,
|
expectedPriority: 100000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "One Path in matches",
|
desc: "One Path",
|
||||||
routeMatches: []gatev1.HTTPRouteMatch{
|
routeMatch: gatev1.HTTPRouteMatch{
|
||||||
{
|
Path: &gatev1.HTTPPathMatch{
|
||||||
Path: &gatev1.HTTPPathMatch{
|
Type: ptr.To(gatev1.PathMatchExact),
|
||||||
Type: ptr.To(gatev1.PathMatchExact),
|
Value: ptr.To("/foo/"),
|
||||||
Value: ptr.To("/foo/"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedRule: "Path(`/foo/`)",
|
expectedRule: "Path(`/foo/`)",
|
||||||
expectedPriority: 99990,
|
expectedPriority: 100000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "One Path in matches and another empty",
|
desc: "Path && Header",
|
||||||
routeMatches: []gatev1.HTTPRouteMatch{
|
routeMatch: gatev1.HTTPRouteMatch{
|
||||||
{
|
Path: &gatev1.HTTPPathMatch{
|
||||||
Path: &gatev1.HTTPPathMatch{
|
Type: ptr.To(gatev1.PathMatchExact),
|
||||||
Type: ptr.To(gatev1.PathMatchExact),
|
Value: ptr.To("/foo/"),
|
||||||
Value: ptr.To("/foo/"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{},
|
Headers: []gatev1.HTTPHeaderMatch{
|
||||||
},
|
{
|
||||||
expectedRule: "Path(`/foo/`) || PathPrefix(`/`)",
|
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||||
expectedPriority: 99980,
|
Name: "my-header",
|
||||||
},
|
Value: "foo",
|
||||||
{
|
|
||||||
desc: "Path OR Header rules",
|
|
||||||
routeMatches: []gatev1.HTTPRouteMatch{
|
|
||||||
{
|
|
||||||
Path: &gatev1.HTTPPathMatch{
|
|
||||||
Type: ptr.To(gatev1.PathMatchExact),
|
|
||||||
Value: ptr.To("/foo/"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Headers: []gatev1.HTTPHeaderMatch{
|
|
||||||
{
|
|
||||||
Type: ptr.To(gatev1.HeaderMatchExact),
|
|
||||||
Name: "my-header",
|
|
||||||
Value: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRule: "Path(`/foo/`) || PathPrefix(`/`) && Header(`my-header`,`foo`)",
|
|
||||||
expectedPriority: 99980,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Path && Header rules",
|
|
||||||
routeMatches: []gatev1.HTTPRouteMatch{
|
|
||||||
{
|
|
||||||
Path: &gatev1.HTTPPathMatch{
|
|
||||||
Type: ptr.To(gatev1.PathMatchExact),
|
|
||||||
Value: ptr.To("/foo/"),
|
|
||||||
},
|
|
||||||
Headers: []gatev1.HTTPHeaderMatch{
|
|
||||||
{
|
|
||||||
Type: ptr.To(gatev1.HeaderMatchExact),
|
|
||||||
Name: "my-header",
|
|
||||||
Value: "foo",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedRule: "Path(`/foo/`) && Header(`my-header`,`foo`)",
|
expectedRule: "Path(`/foo/`) && Header(`my-header`,`foo`)",
|
||||||
expectedPriority: 100090,
|
expectedPriority: 100100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Host && Path && Header rules",
|
desc: "Host && Path && Header",
|
||||||
hostnames: []gatev1.Hostname{"foo.com"},
|
hostnames: []gatev1.Hostname{"foo.com"},
|
||||||
routeMatches: []gatev1.HTTPRouteMatch{
|
routeMatch: gatev1.HTTPRouteMatch{
|
||||||
{
|
Path: &gatev1.HTTPPathMatch{
|
||||||
Path: &gatev1.HTTPPathMatch{
|
Type: ptr.To(gatev1.PathMatchExact),
|
||||||
Type: ptr.To(gatev1.PathMatchExact),
|
Value: ptr.To("/foo/"),
|
||||||
Value: ptr.To("/foo/"),
|
},
|
||||||
},
|
Headers: []gatev1.HTTPHeaderMatch{
|
||||||
Headers: []gatev1.HTTPHeaderMatch{
|
{
|
||||||
{
|
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||||
Type: ptr.To(gatev1.HeaderMatchExact),
|
Name: "my-header",
|
||||||
Name: "my-header",
|
Value: "foo",
|
||||||
Value: "foo",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedRule: "Host(`foo.com`) && (Path(`/foo/`) && Header(`my-header`,`foo`))",
|
expectedRule: "Host(`foo.com`) && Path(`/foo/`) && Header(`my-header`,`foo`)",
|
||||||
expectedPriority: 100097,
|
expectedPriority: 100107,
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "Host && (Path || Header) rules",
|
|
||||||
hostnames: []gatev1.Hostname{"foo.com"},
|
|
||||||
routeMatches: []gatev1.HTTPRouteMatch{
|
|
||||||
{
|
|
||||||
Path: &gatev1.HTTPPathMatch{
|
|
||||||
Type: ptr.To(gatev1.PathMatchExact),
|
|
||||||
Value: ptr.To("/foo/"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Headers: []gatev1.HTTPHeaderMatch{
|
|
||||||
{
|
|
||||||
Type: ptr.To(gatev1.HeaderMatchExact),
|
|
||||||
Name: "my-header",
|
|
||||||
Value: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRule: "Host(`foo.com`) && (Path(`/foo/`) || PathPrefix(`/`) && Header(`my-header`,`foo`))",
|
|
||||||
expectedPriority: 99987,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,7 +197,7 @@ func Test_buildRouterRule(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
rule, priority := buildRouterRule(test.hostnames, test.routeMatches)
|
rule, priority := buildMatchRule(test.hostnames, test.routeMatch)
|
||||||
assert.Equal(t, test.expectedRule, rule)
|
assert.Equal(t, test.expectedRule, rule)
|
||||||
assert.Equal(t, test.expectedPriority, priority)
|
assert.Equal(t, test.expectedPriority, priority)
|
||||||
})
|
})
|
||||||
|
|
|
@ -68,6 +68,7 @@ type Provider struct {
|
||||||
lastConfiguration safe.Safe
|
lastConfiguration safe.Safe
|
||||||
|
|
||||||
routerTransform k8s.RouterTransform
|
routerTransform k8s.RouterTransform
|
||||||
|
client *clientWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entrypoint defines the available entry points.
|
// Entrypoint defines the available entry points.
|
||||||
|
@ -194,6 +195,14 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
|
||||||
|
|
||||||
// Init the provider.
|
// Init the provider.
|
||||||
func (p *Provider) Init() error {
|
func (p *Provider) Init() error {
|
||||||
|
logger := log.With().Str(logs.ProviderName, providerName).Logger()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
p.client, err = p.newK8sClient(logger.WithContext(context.Background()))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating k8s client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,14 +211,9 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||||
logger := log.With().Str(logs.ProviderName, providerName).Logger()
|
logger := log.With().Str(logs.ProviderName, providerName).Logger()
|
||||||
ctxLog := logger.WithContext(context.Background())
|
ctxLog := logger.WithContext(context.Background())
|
||||||
|
|
||||||
k8sClient, err := p.newK8sClient(ctxLog)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pool.GoCtx(func(ctxPool context.Context) {
|
pool.GoCtx(func(ctxPool context.Context) {
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done())
|
eventsChan, err := p.client.WatchAll(p.Namespaces, ctxPool.Done())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error().Err(err).Msg("Error watching kubernetes events")
|
logger.Error().Err(err).Msg("Error watching kubernetes events")
|
||||||
timer := time.NewTimer(1 * time.Second)
|
timer := time.NewTimer(1 * time.Second)
|
||||||
|
@ -235,7 +239,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||||
// Note that event is the *first* event that came in during this throttling interval -- if we're hitting our throttle, we may have dropped events.
|
// Note that event is the *first* event that came in during this throttling interval -- if we're hitting our throttle, we may have dropped events.
|
||||||
// This is fine, because we don't treat different event types differently.
|
// This is fine, because we don't treat different event types differently.
|
||||||
// But if we do in the future, we'll need to track more information about the dropped events.
|
// But if we do in the future, we'll need to track more information about the dropped events.
|
||||||
conf := p.loadConfigurationFromGateways(ctxLog, k8sClient)
|
conf := p.loadConfigurationFromGateways(ctxLog)
|
||||||
|
|
||||||
confHash, err := hashstructure.Hash(conf, nil)
|
confHash, err := hashstructure.Hash(conf, nil)
|
||||||
switch {
|
switch {
|
||||||
|
@ -272,7 +276,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Handle errors and update resources statuses (gatewayClass, gateway).
|
// TODO Handle errors and update resources statuses (gatewayClass, gateway).
|
||||||
func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Client) *dynamic.Configuration {
|
func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.Configuration {
|
||||||
conf := &dynamic.Configuration{
|
conf := &dynamic.Configuration{
|
||||||
HTTP: &dynamic.HTTPConfiguration{
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
Routers: map[string]*dynamic.Router{},
|
Routers: map[string]*dynamic.Router{},
|
||||||
|
@ -293,13 +297,13 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
||||||
TLS: &dynamic.TLSConfiguration{},
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
}
|
}
|
||||||
|
|
||||||
addresses, err := p.gatewayAddresses(client)
|
addresses, err := p.gatewayAddresses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Ctx(ctx).Error().Err(err).Msg("Unable to get Gateway status addresses")
|
log.Ctx(ctx).Error().Err(err).Msg("Unable to get Gateway status addresses")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
gatewayClasses, err := client.ListGatewayClasses()
|
gatewayClasses, err := p.client.ListGatewayClasses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Ctx(ctx).Error().Err(err).Msg("Unable to list GatewayClasses")
|
log.Ctx(ctx).Error().Err(err).Msg("Unable to list GatewayClasses")
|
||||||
return nil
|
return nil
|
||||||
|
@ -313,7 +317,7 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
||||||
|
|
||||||
gatewayClassNames[gatewayClass.Name] = struct{}{}
|
gatewayClassNames[gatewayClass.Name] = struct{}{}
|
||||||
|
|
||||||
err := client.UpdateGatewayClassStatus(gatewayClass, metav1.Condition{
|
err := p.client.UpdateGatewayClassStatus(gatewayClass, metav1.Condition{
|
||||||
Type: string(gatev1.GatewayClassConditionStatusAccepted),
|
Type: string(gatev1.GatewayClassConditionStatusAccepted),
|
||||||
Status: metav1.ConditionTrue,
|
Status: metav1.ConditionTrue,
|
||||||
ObservedGeneration: gatewayClass.Generation,
|
ObservedGeneration: gatewayClass.Generation,
|
||||||
|
@ -330,7 +334,7 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gateways := client.ListGateways()
|
gateways := p.client.ListGateways()
|
||||||
|
|
||||||
var gatewayListeners []gatewayListener
|
var gatewayListeners []gatewayListener
|
||||||
for _, gateway := range gateways {
|
for _, gateway := range gateways {
|
||||||
|
@ -343,14 +347,14 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
gatewayListeners = append(gatewayListeners, p.loadGatewayListeners(logger.WithContext(ctx), client, gateway, conf)...)
|
gatewayListeners = append(gatewayListeners, p.loadGatewayListeners(logger.WithContext(ctx), gateway, conf)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.loadHTTPRoutes(ctx, client, gatewayListeners, conf)
|
p.loadHTTPRoutes(ctx, gatewayListeners, conf)
|
||||||
|
|
||||||
if p.ExperimentalChannel {
|
if p.ExperimentalChannel {
|
||||||
p.loadTCPRoutes(ctx, client, gatewayListeners, conf)
|
p.loadTCPRoutes(ctx, gatewayListeners, conf)
|
||||||
p.loadTLSRoutes(ctx, client, gatewayListeners, conf)
|
p.loadTLSRoutes(ctx, gatewayListeners, conf)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, gateway := range gateways {
|
for _, gateway := range gateways {
|
||||||
|
@ -367,7 +371,7 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
||||||
}
|
}
|
||||||
|
|
||||||
gatewayStatus, errG := p.makeGatewayStatus(gateway, listeners, addresses)
|
gatewayStatus, errG := p.makeGatewayStatus(gateway, listeners, addresses)
|
||||||
if err = client.UpdateGatewayStatus(gateway, gatewayStatus); err != nil {
|
if err = p.client.UpdateGatewayStatus(gateway, gatewayStatus); err != nil {
|
||||||
logger.Error().
|
logger.Error().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Unable to update Gateway status")
|
Msg("Unable to update Gateway status")
|
||||||
|
@ -382,7 +386,7 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
||||||
return conf
|
return conf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) loadGatewayListeners(ctx context.Context, client Client, gateway *gatev1.Gateway, conf *dynamic.Configuration) []gatewayListener {
|
func (p *Provider) loadGatewayListeners(ctx context.Context, gateway *gatev1.Gateway, conf *dynamic.Configuration) []gatewayListener {
|
||||||
tlsConfigs := make(map[string]*tls.CertAndStores)
|
tlsConfigs := make(map[string]*tls.CertAndStores)
|
||||||
allocatedListeners := make(map[string]struct{})
|
allocatedListeners := make(map[string]struct{})
|
||||||
gatewayListeners := make([]gatewayListener, len(gateway.Spec.Listeners))
|
gatewayListeners := make([]gatewayListener, len(gateway.Spec.Listeners))
|
||||||
|
@ -420,7 +424,7 @@ func (p *Provider) loadGatewayListeners(ctx context.Context, client Client, gate
|
||||||
gatewayListeners[i].EPName = ep
|
gatewayListeners[i].EPName = ep
|
||||||
|
|
||||||
allowedRoutes := ptr.Deref(listener.AllowedRoutes, gatev1.AllowedRoutes{Namespaces: &gatev1.RouteNamespaces{From: ptr.To(gatev1.NamespacesFromSame)}})
|
allowedRoutes := ptr.Deref(listener.AllowedRoutes, gatev1.AllowedRoutes{Namespaces: &gatev1.RouteNamespaces{From: ptr.To(gatev1.NamespacesFromSame)}})
|
||||||
gatewayListeners[i].AllowedNamespaces, err = allowedNamespaces(client, gateway.Namespace, allowedRoutes.Namespaces)
|
gatewayListeners[i].AllowedNamespaces, err = p.allowedNamespaces(gateway.Namespace, allowedRoutes.Namespaces)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
|
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
|
||||||
gatewayListeners[i].Status.Conditions = append(gatewayListeners[i].Status.Conditions, metav1.Condition{
|
gatewayListeners[i].Status.Conditions = append(gatewayListeners[i].Status.Conditions, metav1.Condition{
|
||||||
|
@ -565,7 +569,7 @@ func (p *Provider) loadGatewayListeners(ctx context.Context, client Client, gate
|
||||||
certificateNamespace = string(*certificateRef.Namespace)
|
certificateNamespace = string(*certificateRef.Namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := isReferenceGranted(client, groupGateway, kindGateway, gateway.Namespace, groupCore, "Secret", string(certificateRef.Name), certificateNamespace); err != nil {
|
if err := p.isReferenceGranted(groupGateway, kindGateway, gateway.Namespace, groupCore, "Secret", string(certificateRef.Name), certificateNamespace); err != nil {
|
||||||
gatewayListeners[i].Status.Conditions = append(gatewayListeners[i].Status.Conditions, metav1.Condition{
|
gatewayListeners[i].Status.Conditions = append(gatewayListeners[i].Status.Conditions, metav1.Condition{
|
||||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
|
@ -580,7 +584,7 @@ func (p *Provider) loadGatewayListeners(ctx context.Context, client Client, gate
|
||||||
|
|
||||||
configKey := certificateNamespace + "/" + string(certificateRef.Name)
|
configKey := certificateNamespace + "/" + string(certificateRef.Name)
|
||||||
if _, tlsExists := tlsConfigs[configKey]; !tlsExists {
|
if _, tlsExists := tlsConfigs[configKey]; !tlsExists {
|
||||||
tlsConf, err := getTLS(client, certificateRef.Name, certificateNamespace)
|
tlsConf, err := p.getTLS(certificateRef.Name, certificateNamespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// update "ResolvedRefs" status false with "InvalidCertificateRef" reason
|
// update "ResolvedRefs" status false with "InvalidCertificateRef" reason
|
||||||
// update "Programmed" status false with "Invalid" reason
|
// update "Programmed" status false with "Invalid" reason
|
||||||
|
@ -703,7 +707,7 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listeners []gatewa
|
||||||
return gatewayStatus, nil
|
return gatewayStatus, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) gatewayAddresses(client Client) ([]gatev1.GatewayStatusAddress, error) {
|
func (p *Provider) gatewayAddresses() ([]gatev1.GatewayStatusAddress, error) {
|
||||||
if p.StatusAddress == nil {
|
if p.StatusAddress == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -724,7 +728,7 @@ func (p *Provider) gatewayAddresses(client Client) ([]gatev1.GatewayStatusAddres
|
||||||
|
|
||||||
svcRef := p.StatusAddress.Service
|
svcRef := p.StatusAddress.Service
|
||||||
if svcRef.Name != "" && svcRef.Namespace != "" {
|
if svcRef.Name != "" && svcRef.Namespace != "" {
|
||||||
svc, exists, err := client.GetService(svcRef.Namespace, svcRef.Name)
|
svc, exists, err := p.client.GetService(svcRef.Namespace, svcRef.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to get service: %w", err)
|
return nil, fmt.Errorf("unable to get service: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -772,6 +776,71 @@ func (p *Provider) entryPointName(port gatev1.PortNumber, protocol gatev1.Protoc
|
||||||
return "", fmt.Errorf("no matching entryPoint for port %d and protocol %q", port, protocol)
|
return "", fmt.Errorf("no matching entryPoint for port %d and protocol %q", port, protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) isReferenceGranted(fromGroup, fromKind, fromNamespace, toGroup, toKind, toName, toNamespace string) error {
|
||||||
|
if toNamespace == fromNamespace {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
refGrants, err := p.client.ListReferenceGrants(toNamespace)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listing ReferenceGrant: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
refGrants = filterReferenceGrantsFrom(refGrants, fromGroup, fromKind, fromNamespace)
|
||||||
|
refGrants = filterReferenceGrantsTo(refGrants, toGroup, toKind, toName)
|
||||||
|
if len(refGrants) == 0 {
|
||||||
|
return errors.New("missing ReferenceGrant")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) getTLS(secretName gatev1.ObjectName, namespace string) (*tls.CertAndStores, error) {
|
||||||
|
secret, exists, err := p.client.GetSecret(namespace, string(secretName))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch secret %s/%s: %w", namespace, secretName, err)
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("secret %s/%s does not exist", namespace, secretName)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, key, err := getCertificateBlocks(secret, namespace, string(secretName))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tls.CertAndStores{
|
||||||
|
Certificate: tls.Certificate{
|
||||||
|
CertFile: types.FileOrContent(cert),
|
||||||
|
KeyFile: types.FileOrContent(key),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) allowedNamespaces(gatewayNamespace string, routeNamespaces *gatev1.RouteNamespaces) ([]string, error) {
|
||||||
|
if routeNamespaces == nil || routeNamespaces.From == nil {
|
||||||
|
return []string{gatewayNamespace}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch *routeNamespaces.From {
|
||||||
|
case gatev1.NamespacesFromAll:
|
||||||
|
return []string{metav1.NamespaceAll}, nil
|
||||||
|
|
||||||
|
case gatev1.NamespacesFromSame:
|
||||||
|
return []string{gatewayNamespace}, nil
|
||||||
|
|
||||||
|
case gatev1.NamespacesFromSelector:
|
||||||
|
selector, err := metav1.LabelSelectorAsSelector(routeNamespaces.Selector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("malformed selector: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.client.ListNamespaces(selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unsupported RouteSelectType: %q", *routeNamespaces.From)
|
||||||
|
}
|
||||||
|
|
||||||
func supportedRouteKinds(protocol gatev1.ProtocolType, experimentalChannel bool) ([]gatev1.RouteGroupKind, []metav1.Condition) {
|
func supportedRouteKinds(protocol gatev1.ProtocolType, experimentalChannel bool) ([]gatev1.RouteGroupKind, []metav1.Condition) {
|
||||||
group := gatev1.Group(gatev1.GroupName)
|
group := gatev1.Group(gatev1.GroupName)
|
||||||
|
|
||||||
|
@ -856,30 +925,6 @@ func allowedRouteKinds(gateway *gatev1.Gateway, listener gatev1.Listener, suppor
|
||||||
return routeKinds, conditions
|
return routeKinds, conditions
|
||||||
}
|
}
|
||||||
|
|
||||||
func allowedNamespaces(client Client, gatewayNamespace string, routeNamespaces *gatev1.RouteNamespaces) ([]string, error) {
|
|
||||||
if routeNamespaces == nil || routeNamespaces.From == nil {
|
|
||||||
return []string{gatewayNamespace}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch *routeNamespaces.From {
|
|
||||||
case gatev1.NamespacesFromAll:
|
|
||||||
return []string{metav1.NamespaceAll}, nil
|
|
||||||
|
|
||||||
case gatev1.NamespacesFromSame:
|
|
||||||
return []string{gatewayNamespace}, nil
|
|
||||||
|
|
||||||
case gatev1.NamespacesFromSelector:
|
|
||||||
selector, err := metav1.LabelSelectorAsSelector(routeNamespaces.Selector)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("malformed selector: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.ListNamespaces(selector)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("unsupported RouteSelectType: %q", *routeNamespaces.From)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findMatchingHostnames(listenerHostname *gatev1.Hostname, routeHostnames []gatev1.Hostname) ([]gatev1.Hostname, bool) {
|
func findMatchingHostnames(listenerHostname *gatev1.Hostname, routeHostnames []gatev1.Hostname) ([]gatev1.Hostname, bool) {
|
||||||
if listenerHostname == nil {
|
if listenerHostname == nil {
|
||||||
return routeHostnames, true
|
return routeHostnames, true
|
||||||
|
@ -971,7 +1016,7 @@ func matchListener(listener gatewayListener, routeNamespace string, parentRef ga
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeRouterKey(rule, name string) string {
|
func makeRouterName(rule, name string) string {
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
|
|
||||||
// As explained in https://pkg.go.dev/hash#Hash,
|
// As explained in https://pkg.go.dev/hash#Hash,
|
||||||
|
@ -981,36 +1026,6 @@ func makeRouterKey(rule, name string) string {
|
||||||
return fmt.Sprintf("%s-%.10x", name, h.Sum(nil))
|
return fmt.Sprintf("%s-%.10x", name, h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeID(namespace, name string) string {
|
|
||||||
if namespace == "" {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
return namespace + "-" + name
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTLS(client Client, secretName gatev1.ObjectName, namespace string) (*tls.CertAndStores, error) {
|
|
||||||
secret, exists, err := client.GetSecret(namespace, string(secretName))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to fetch secret %s/%s: %w", namespace, secretName, err)
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
return nil, fmt.Errorf("secret %s/%s does not exist", namespace, secretName)
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, key, err := getCertificateBlocks(secret, namespace, string(secretName))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tls.CertAndStores{
|
|
||||||
Certificate: tls.Certificate{
|
|
||||||
CertFile: types.FileOrContent(cert),
|
|
||||||
KeyFile: types.FileOrContent(key),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTLSConfig(tlsConfigs map[string]*tls.CertAndStores) []*tls.CertAndStores {
|
func getTLSConfig(tlsConfigs map[string]*tls.CertAndStores) []*tls.CertAndStores {
|
||||||
var secretNames []string
|
var secretNames []string
|
||||||
for secretName := range tlsConfigs {
|
for secretName := range tlsConfigs {
|
||||||
|
@ -1114,25 +1129,6 @@ func makeListenerKey(l gatev1.Listener) string {
|
||||||
return fmt.Sprintf("%s|%s|%d", l.Protocol, hostname, l.Port)
|
return fmt.Sprintf("%s|%s|%d", l.Protocol, hostname, l.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isReferenceGranted(client Client, fromGroup, fromKind, fromNamespace, toGroup, toKind, toName, toNamespace string) error {
|
|
||||||
if toNamespace == fromNamespace {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
refGrants, err := client.ListReferenceGrants(toNamespace)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("listing ReferenceGrant: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
refGrants = filterReferenceGrantsFrom(refGrants, fromGroup, fromKind, fromNamespace)
|
|
||||||
refGrants = filterReferenceGrantsTo(refGrants, toGroup, toKind, toName)
|
|
||||||
if len(refGrants) == 0 {
|
|
||||||
return errors.New("missing ReferenceGrant")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterReferenceGrantsFrom(referenceGrants []*gatev1beta1.ReferenceGrant, group, kind, namespace string) []*gatev1beta1.ReferenceGrant {
|
func filterReferenceGrantsFrom(referenceGrants []*gatev1beta1.ReferenceGrant, group, kind, namespace string) []*gatev1beta1.ReferenceGrant {
|
||||||
var matchingReferenceGrants []*gatev1beta1.ReferenceGrant
|
var matchingReferenceGrants []*gatev1beta1.ReferenceGrant
|
||||||
for _, referenceGrant := range referenceGrants {
|
for _, referenceGrant := range referenceGrants {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -18,9 +18,9 @@ import (
|
||||||
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Provider) loadTCPRoutes(ctx context.Context, client Client, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
func (p *Provider) loadTCPRoutes(ctx context.Context, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
||||||
logger := log.Ctx(ctx)
|
logger := log.Ctx(ctx)
|
||||||
routes, err := client.ListTCPRoutes()
|
routes, err := p.client.ListTCPRoutes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error().Err(err).Msgf("Unable to list TCPRoutes")
|
logger.Error().Err(err).Msgf("Unable to list TCPRoutes")
|
||||||
return
|
return
|
||||||
|
@ -64,7 +64,7 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, client Client, gatewayList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
routeConf, resolveRefCondition := p.loadTCPRoute(client, listener, route)
|
routeConf, resolveRefCondition := p.loadTCPRoute(listener, route)
|
||||||
if accepted && listener.Attached {
|
if accepted && listener.Attached {
|
||||||
mergeTCPConfiguration(routeConf, conf)
|
mergeTCPConfiguration(routeConf, conf)
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, client Client, gatewayList
|
||||||
Parents: parentStatuses,
|
Parents: parentStatuses,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := client.UpdateTCPRouteStatus(ctx, ktypes.NamespacedName{Namespace: route.Namespace, Name: route.Name}, routeStatus); err != nil {
|
if err := p.client.UpdateTCPRouteStatus(ctx, ktypes.NamespacedName{Namespace: route.Namespace, Name: route.Name}, routeStatus); err != nil {
|
||||||
logger.Error().
|
logger.Error().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Unable to update TCPRoute status")
|
Msg("Unable to update TCPRoute status")
|
||||||
|
@ -87,8 +87,8 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, client Client, gatewayList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) loadTCPRoute(client Client, listener gatewayListener, route *gatev1alpha2.TCPRoute) (*dynamic.Configuration, metav1.Condition) {
|
func (p *Provider) loadTCPRoute(listener gatewayListener, route *gatev1alpha2.TCPRoute) (*dynamic.Configuration, metav1.Condition) {
|
||||||
routeConf := &dynamic.Configuration{
|
conf := &dynamic.Configuration{
|
||||||
TCP: &dynamic.TCPConfiguration{
|
TCP: &dynamic.TCPConfiguration{
|
||||||
Routers: make(map[string]*dynamic.TCPRouter),
|
Routers: make(map[string]*dynamic.TCPRouter),
|
||||||
Middlewares: make(map[string]*dynamic.TCPMiddleware),
|
Middlewares: make(map[string]*dynamic.TCPMiddleware),
|
||||||
|
@ -97,7 +97,7 @@ func (p *Provider) loadTCPRoute(client Client, listener gatewayListener, route *
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
routeCondition := metav1.Condition{
|
condition := metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionTrue,
|
Status: metav1.ConditionTrue,
|
||||||
ObservedGeneration: route.Generation,
|
ObservedGeneration: route.Generation,
|
||||||
|
@ -119,8 +119,7 @@ func (p *Provider) loadTCPRoute(client Client, listener gatewayListener, route *
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
||||||
routerName := route.Name + "-" + listener.GWName + "-" + listener.EPName
|
routerName := provider.Normalize(route.Namespace + "-" + route.Name + "-" + listener.GWName + "-" + listener.EPName)
|
||||||
routerKey := provider.Normalize(makeRouterKey("", makeID(route.Namespace, routerName)))
|
|
||||||
|
|
||||||
var ruleServiceNames []string
|
var ruleServiceNames []string
|
||||||
for i, rule := range route.Spec.Rules {
|
for i, rule := range route.Spec.Rules {
|
||||||
|
@ -130,9 +129,9 @@ func (p *Provider) loadTCPRoute(client Client, listener gatewayListener, route *
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
wrrService, subServices, err := loadTCPServices(client, route.Namespace, rule.BackendRefs)
|
wrrService, subServices, err := p.loadTCPServices(route.Namespace, rule.BackendRefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return routeConf, metav1.Condition{
|
return conf, metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
ObservedGeneration: route.Generation,
|
ObservedGeneration: route.Generation,
|
||||||
|
@ -143,22 +142,22 @@ func (p *Provider) loadTCPRoute(client Client, listener gatewayListener, route *
|
||||||
}
|
}
|
||||||
|
|
||||||
for svcName, svc := range subServices {
|
for svcName, svc := range subServices {
|
||||||
routeConf.TCP.Services[svcName] = svc
|
conf.TCP.Services[svcName] = svc
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceName := fmt.Sprintf("%s-wrr-%d", routerKey, i)
|
serviceName := fmt.Sprintf("%s-wrr-%d", routerName, i)
|
||||||
routeConf.TCP.Services[serviceName] = wrrService
|
conf.TCP.Services[serviceName] = wrrService
|
||||||
|
|
||||||
ruleServiceNames = append(ruleServiceNames, serviceName)
|
ruleServiceNames = append(ruleServiceNames, serviceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ruleServiceNames) == 1 {
|
if len(ruleServiceNames) == 1 {
|
||||||
router.Service = ruleServiceNames[0]
|
router.Service = ruleServiceNames[0]
|
||||||
routeConf.TCP.Routers[routerKey] = &router
|
conf.TCP.Routers[routerName] = &router
|
||||||
return routeConf, routeCondition
|
return conf, condition
|
||||||
}
|
}
|
||||||
|
|
||||||
routeServiceKey := routerKey + "-wrr"
|
routeServiceKey := routerName + "-wrr"
|
||||||
routeService := &dynamic.TCPService{Weighted: &dynamic.TCPWeightedRoundRobin{}}
|
routeService := &dynamic.TCPService{Weighted: &dynamic.TCPWeightedRoundRobin{}}
|
||||||
|
|
||||||
for _, name := range ruleServiceNames {
|
for _, name := range ruleServiceNames {
|
||||||
|
@ -168,16 +167,16 @@ func (p *Provider) loadTCPRoute(client Client, listener gatewayListener, route *
|
||||||
routeService.Weighted.Services = append(routeService.Weighted.Services, service)
|
routeService.Weighted.Services = append(routeService.Weighted.Services, service)
|
||||||
}
|
}
|
||||||
|
|
||||||
routeConf.TCP.Services[routeServiceKey] = routeService
|
conf.TCP.Services[routeServiceKey] = routeService
|
||||||
|
|
||||||
router.Service = routeServiceKey
|
router.Service = routeServiceKey
|
||||||
routeConf.TCP.Routers[routerKey] = &router
|
conf.TCP.Routers[routerName] = &router
|
||||||
|
|
||||||
return routeConf, routeCondition
|
return conf, condition
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadTCPServices is generating a WRR service, even when there is only one target.
|
// loadTCPServices is generating a WRR service, even when there is only one target.
|
||||||
func loadTCPServices(client Client, namespace string, backendRefs []gatev1.BackendRef) (*dynamic.TCPService, map[string]*dynamic.TCPService, error) {
|
func (p *Provider) loadTCPServices(namespace string, backendRefs []gatev1.BackendRef) (*dynamic.TCPService, map[string]*dynamic.TCPService, error) {
|
||||||
services := map[string]*dynamic.TCPService{}
|
services := map[string]*dynamic.TCPService{}
|
||||||
|
|
||||||
wrrSvc := &dynamic.TCPService{
|
wrrSvc := &dynamic.TCPService{
|
||||||
|
@ -211,7 +210,7 @@ func loadTCPServices(client Client, namespace string, backendRefs []gatev1.Backe
|
||||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{},
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{},
|
||||||
}
|
}
|
||||||
|
|
||||||
service, exists, err := client.GetService(namespace, string(backendRef.Name))
|
service, exists, err := p.client.GetService(namespace, string(backendRef.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -247,7 +246,7 @@ func loadTCPServices(client Client, namespace string, backendRefs []gatev1.Backe
|
||||||
return nil, nil, errors.New("service port not found")
|
return nil, nil, errors.New("service port not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, string(backendRef.Name))
|
endpoints, endpointsExists, endpointsErr := p.client.GetEndpoints(namespace, string(backendRef.Name))
|
||||||
if endpointsErr != nil {
|
if endpointsErr != nil {
|
||||||
return nil, nil, endpointsErr
|
return nil, nil, endpointsErr
|
||||||
}
|
}
|
||||||
|
@ -282,7 +281,7 @@ func loadTCPServices(client Client, namespace string, backendRefs []gatev1.Backe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceName := provider.Normalize(makeID(service.Namespace, service.Name) + "-" + portStr)
|
serviceName := provider.Normalize(service.Namespace + "-" + service.Name + "-" + portStr)
|
||||||
services[serviceName] = &svc
|
services[serviceName] = &svc
|
||||||
|
|
||||||
wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.TCPWRRService{Name: serviceName, Weight: &weight})
|
wrrSvc.Weighted.Services = append(wrrSvc.Weighted.Services, dynamic.TCPWRRService{Name: serviceName, Weight: &weight})
|
||||||
|
|
|
@ -15,9 +15,9 @@ import (
|
||||||
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Provider) loadTLSRoutes(ctx context.Context, client Client, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
func (p *Provider) loadTLSRoutes(ctx context.Context, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
||||||
logger := log.Ctx(ctx)
|
logger := log.Ctx(ctx)
|
||||||
routes, err := client.ListTLSRoutes()
|
routes, err := p.client.ListTLSRoutes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error().Err(err).Msgf("Unable to list TLSRoute")
|
logger.Error().Err(err).Msgf("Unable to list TLSRoute")
|
||||||
return
|
return
|
||||||
|
@ -66,7 +66,7 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, client Client, gatewayList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
routeConf, resolveRefCondition := p.loadTLSRoute(client, listener, route, hostnames)
|
routeConf, resolveRefCondition := p.loadTLSRoute(listener, route, hostnames)
|
||||||
if accepted && listener.Attached {
|
if accepted && listener.Attached {
|
||||||
mergeTCPConfiguration(routeConf, conf)
|
mergeTCPConfiguration(routeConf, conf)
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, client Client, gatewayList
|
||||||
Parents: parentStatuses,
|
Parents: parentStatuses,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := client.UpdateTLSRouteStatus(ctx, ktypes.NamespacedName{Namespace: route.Namespace, Name: route.Name}, routeStatus); err != nil {
|
if err := p.client.UpdateTLSRouteStatus(ctx, ktypes.NamespacedName{Namespace: route.Namespace, Name: route.Name}, routeStatus); err != nil {
|
||||||
logger.Error().
|
logger.Error().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Unable to update TLSRoute status")
|
Msg("Unable to update TLSRoute status")
|
||||||
|
@ -89,8 +89,8 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, client Client, gatewayList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) loadTLSRoute(client Client, listener gatewayListener, route *gatev1alpha2.TLSRoute, hostnames []gatev1.Hostname) (*dynamic.Configuration, metav1.Condition) {
|
func (p *Provider) loadTLSRoute(listener gatewayListener, route *gatev1alpha2.TLSRoute, hostnames []gatev1.Hostname) (*dynamic.Configuration, metav1.Condition) {
|
||||||
routeConf := &dynamic.Configuration{
|
conf := &dynamic.Configuration{
|
||||||
TCP: &dynamic.TCPConfiguration{
|
TCP: &dynamic.TCPConfiguration{
|
||||||
Routers: make(map[string]*dynamic.TCPRouter),
|
Routers: make(map[string]*dynamic.TCPRouter),
|
||||||
Middlewares: make(map[string]*dynamic.TCPMiddleware),
|
Middlewares: make(map[string]*dynamic.TCPMiddleware),
|
||||||
|
@ -99,7 +99,7 @@ func (p *Provider) loadTLSRoute(client Client, listener gatewayListener, route *
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
routeCondition := metav1.Condition{
|
condition := metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionTrue,
|
Status: metav1.ConditionTrue,
|
||||||
ObservedGeneration: route.Generation,
|
ObservedGeneration: route.Generation,
|
||||||
|
@ -117,8 +117,8 @@ func (p *Provider) loadTLSRoute(client Client, listener gatewayListener, route *
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
|
||||||
routerName := route.Name + "-" + listener.GWName + "-" + listener.EPName
|
routeKey := provider.Normalize(route.Namespace + "-" + route.Name + "-" + listener.GWName + "-" + listener.EPName)
|
||||||
routerKey := provider.Normalize(makeRouterKey(router.Rule, makeID(route.Namespace, routerName)))
|
routerName := makeRouterName(router.Rule, routeKey)
|
||||||
|
|
||||||
var ruleServiceNames []string
|
var ruleServiceNames []string
|
||||||
for i, routeRule := range route.Spec.Rules {
|
for i, routeRule := range route.Spec.Rules {
|
||||||
|
@ -128,10 +128,10 @@ func (p *Provider) loadTLSRoute(client Client, listener gatewayListener, route *
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
wrrService, subServices, err := loadTCPServices(client, route.Namespace, routeRule.BackendRefs)
|
wrrService, subServices, err := p.loadTCPServices(route.Namespace, routeRule.BackendRefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// update "ResolvedRefs" status true with "InvalidBackendRefs" reason
|
// update "ResolvedRefs" status true with "InvalidBackendRefs" reason
|
||||||
routeCondition = metav1.Condition{
|
condition = metav1.Condition{
|
||||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||||
Status: metav1.ConditionFalse,
|
Status: metav1.ConditionFalse,
|
||||||
ObservedGeneration: route.Generation,
|
ObservedGeneration: route.Generation,
|
||||||
|
@ -143,23 +143,23 @@ func (p *Provider) loadTLSRoute(client Client, listener gatewayListener, route *
|
||||||
}
|
}
|
||||||
|
|
||||||
for svcName, svc := range subServices {
|
for svcName, svc := range subServices {
|
||||||
routeConf.TCP.Services[svcName] = svc
|
conf.TCP.Services[svcName] = svc
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceName := fmt.Sprintf("%s-wrr-%d", routerKey, i)
|
serviceName := fmt.Sprintf("%s-wrr-%d", routerName, i)
|
||||||
routeConf.TCP.Services[serviceName] = wrrService
|
conf.TCP.Services[serviceName] = wrrService
|
||||||
|
|
||||||
ruleServiceNames = append(ruleServiceNames, serviceName)
|
ruleServiceNames = append(ruleServiceNames, serviceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ruleServiceNames) == 1 {
|
if len(ruleServiceNames) == 1 {
|
||||||
router.Service = ruleServiceNames[0]
|
router.Service = ruleServiceNames[0]
|
||||||
routeConf.TCP.Routers[routerKey] = &router
|
conf.TCP.Routers[routerName] = &router
|
||||||
|
|
||||||
return routeConf, routeCondition
|
return conf, condition
|
||||||
}
|
}
|
||||||
|
|
||||||
routeServiceKey := routerKey + "-wrr"
|
routeServiceKey := routerName + "-wrr"
|
||||||
routeService := &dynamic.TCPService{Weighted: &dynamic.TCPWeightedRoundRobin{}}
|
routeService := &dynamic.TCPService{Weighted: &dynamic.TCPWeightedRoundRobin{}}
|
||||||
|
|
||||||
for _, name := range ruleServiceNames {
|
for _, name := range ruleServiceNames {
|
||||||
|
@ -169,12 +169,12 @@ func (p *Provider) loadTLSRoute(client Client, listener gatewayListener, route *
|
||||||
routeService.Weighted.Services = append(routeService.Weighted.Services, service)
|
routeService.Weighted.Services = append(routeService.Weighted.Services, service)
|
||||||
}
|
}
|
||||||
|
|
||||||
routeConf.TCP.Services[routeServiceKey] = routeService
|
conf.TCP.Services[routeServiceKey] = routeService
|
||||||
|
|
||||||
router.Service = routeServiceKey
|
router.Service = routeServiceKey
|
||||||
routeConf.TCP.Routers[routerKey] = &router
|
conf.TCP.Routers[routerName] = &router
|
||||||
|
|
||||||
return routeConf, routeCondition
|
return conf, condition
|
||||||
}
|
}
|
||||||
|
|
||||||
func hostSNIRule(hostnames []gatev1.Hostname) string {
|
func hostSNIRule(hostnames []gatev1.Hostname) string {
|
||||||
|
|
Loading…
Reference in a new issue