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.SupportHTTPRouteHostRewrite,
|
||||
ksuite.SupportHTTPRoutePathRewrite,
|
||||
ksuite.SupportHTTPRoutePathRedirect,
|
||||
),
|
||||
ExemptFeatures: sets.New(
|
||||
ksuite.SupportHTTPRouteRequestTimeout,
|
||||
ksuite.SupportHTTPRouteBackendTimeout,
|
||||
ksuite.SupportHTTPRouteResponseHeaderModification,
|
||||
ksuite.SupportHTTPRoutePathRedirect,
|
||||
ksuite.SupportHTTPRouteRequestMirror,
|
||||
ksuite.SupportHTTPRouteRequestMultipleMirrors,
|
||||
),
|
||||
|
|
40
integration/testdata/rawdata-gateway.json
vendored
40
integration/testdata/rawdata-gateway.json
vendored
|
@ -30,27 +30,27 @@
|
|||
"traefik"
|
||||
]
|
||||
},
|
||||
"default-http-app-1-my-gateway-web-af4b9876d1fe36359e27@kubernetesgateway": {
|
||||
"default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06@kubernetesgateway": {
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"service": "default-http-app-1-my-gateway-web-af4b9876d1fe36359e27-wrr",
|
||||
"rule": "Host(`foo.com`) \u0026\u0026 (Path(`/bar`))",
|
||||
"service": "default-http-app-1-my-gateway-web-0-wrr",
|
||||
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": 99997,
|
||||
"priority": 100008,
|
||||
"status": "enabled",
|
||||
"using": [
|
||||
"web"
|
||||
]
|
||||
},
|
||||
"default-http-app-1-my-https-gateway-websecure-af4b9876d1fe36359e27@kubernetesgateway": {
|
||||
"default-http-app-1-my-https-gateway-websecure-0-1c0cf64bde37d9d0df06@kubernetesgateway": {
|
||||
"entryPoints": [
|
||||
"websecure"
|
||||
],
|
||||
"service": "default-http-app-1-my-https-gateway-websecure-af4b9876d1fe36359e27-wrr",
|
||||
"rule": "Host(`foo.com`) \u0026\u0026 (Path(`/bar`))",
|
||||
"service": "default-http-app-1-my-https-gateway-websecure-0-wrr",
|
||||
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": 99997,
|
||||
"priority": 100008,
|
||||
"tls": {},
|
||||
"status": "enabled",
|
||||
"using": [
|
||||
|
@ -96,7 +96,7 @@
|
|||
"dashboard@internal"
|
||||
]
|
||||
},
|
||||
"default-http-app-1-my-gateway-web-af4b9876d1fe36359e27-wrr@kubernetesgateway": {
|
||||
"default-http-app-1-my-gateway-web-0-wrr@kubernetesgateway": {
|
||||
"weighted": {
|
||||
"services": [
|
||||
{
|
||||
|
@ -107,10 +107,10 @@
|
|||
},
|
||||
"status": "enabled",
|
||||
"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": {
|
||||
"services": [
|
||||
{
|
||||
|
@ -121,7 +121,7 @@
|
|||
},
|
||||
"status": "enabled",
|
||||
"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": {
|
||||
|
@ -150,11 +150,11 @@
|
|||
}
|
||||
},
|
||||
"tcpRouters": {
|
||||
"default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb@kubernetesgateway": {
|
||||
"default-tcp-app-1-my-tcp-gateway-footcp@kubernetesgateway": {
|
||||
"entryPoints": [
|
||||
"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(`*`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": -1,
|
||||
|
@ -163,11 +163,11 @@
|
|||
"footcp"
|
||||
]
|
||||
},
|
||||
"default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb@kubernetesgateway": {
|
||||
"default-tcp-app-1-my-tls-gateway-footlsterminate@kubernetesgateway": {
|
||||
"entryPoints": [
|
||||
"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(`*`)",
|
||||
"ruleSyntax": "v3",
|
||||
"priority": -1,
|
||||
|
@ -197,7 +197,7 @@
|
|||
}
|
||||
},
|
||||
"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": {
|
||||
"services": [
|
||||
{
|
||||
|
@ -208,10 +208,10 @@
|
|||
},
|
||||
"status": "enabled",
|
||||
"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": {
|
||||
"services": [
|
||||
{
|
||||
|
@ -222,7 +222,7 @@
|
|||
},
|
||||
"status": "enabled",
|
||||
"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": {
|
||||
|
|
|
@ -696,7 +696,10 @@ type RequestHeaderModifier struct {
|
|||
|
||||
// RequestRedirect holds the request redirect middleware configuration.
|
||||
type RequestRedirect struct {
|
||||
Regex string `json:"regex,omitempty"`
|
||||
Replacement string `json:"replacement,omitempty"`
|
||||
Permanent bool `json:"permanent,omitempty"`
|
||||
Scheme *string `json:"scheme,omitempty"`
|
||||
Hostname *string `json:"hostname,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 {
|
||||
in, out := &in.RequestRedirect, &out.RequestRedirect
|
||||
*out = new(RequestRedirect)
|
||||
**out = **in
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
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.
|
||||
func (in *RequestRedirect) DeepCopyInto(out *RequestRedirect) {
|
||||
*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
|
||||
}
|
||||
|
||||
|
|
|
@ -2,133 +2,101 @@ package redirect
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
schemeHTTP = "http"
|
||||
schemeHTTPS = "https"
|
||||
typeName = "RequestRedirect"
|
||||
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.
|
||||
func NewRequestRedirect(ctx context.Context, next http.Handler, conf dynamic.RequestRedirect, name string) (http.Handler, error) {
|
||||
logger := middlewares.GetLogger(ctx, name, typeName)
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
statusCode := conf.StatusCode
|
||||
if statusCode == 0 {
|
||||
statusCode = http.StatusFound
|
||||
}
|
||||
if statusCode != http.StatusMovedPermanently && statusCode != http.StatusFound {
|
||||
return nil, fmt.Errorf("unsupported status code: %d", statusCode)
|
||||
}
|
||||
|
||||
return &redirect{
|
||||
regex: re,
|
||||
replacement: conf.Replacement,
|
||||
permanent: conf.Permanent,
|
||||
errHandler: utils.DefaultHandler,
|
||||
next: next,
|
||||
name: name,
|
||||
rawURL: rawURL,
|
||||
return redirect{
|
||||
name: name,
|
||||
next: next,
|
||||
scheme: conf.Scheme,
|
||||
hostname: conf.Hostname,
|
||||
port: conf.Port,
|
||||
path: conf.Path,
|
||||
pathPrefix: conf.PathPrefix,
|
||||
statusCode: statusCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type redirect struct {
|
||||
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) {
|
||||
func (r redirect) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return r.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
oldURL := r.rawURL(req)
|
||||
func (r redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
redirectURL := *req.URL
|
||||
redirectURL.Host = req.Host
|
||||
|
||||
// If the Regexp doesn't match, skip to the next handler.
|
||||
if !r.regex.MatchString(oldURL) {
|
||||
r.next.ServeHTTP(rw, req)
|
||||
return
|
||||
if r.scheme != nil {
|
||||
redirectURL.Scheme = *r.scheme
|
||||
}
|
||||
|
||||
// Apply a rewrite regexp to the URL.
|
||||
newURL := r.regex.ReplaceAllString(oldURL, r.replacement)
|
||||
|
||||
// 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
|
||||
host := redirectURL.Hostname()
|
||||
if r.hostname != nil {
|
||||
host = *r.hostname
|
||||
}
|
||||
|
||||
handler := &moveHandler{location: parsedURL, permanent: r.permanent}
|
||||
handler.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
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
|
||||
port := redirectURL.Port()
|
||||
if r.port != nil {
|
||||
port = *r.port
|
||||
}
|
||||
|
||||
if m.permanent {
|
||||
status = http.StatusMovedPermanently
|
||||
if req.Method != http.MethodGet {
|
||||
status = http.StatusPermanentRedirect
|
||||
if port != "" {
|
||||
host = net.JoinHostPort(host, port)
|
||||
}
|
||||
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)))
|
||||
if err != nil {
|
||||
|
||||
rw.Header().Set("Location", redirectURL.String())
|
||||
|
||||
rw.WriteHeader(r.statusCode)
|
||||
if _, err := rw.Write([]byte(http.StatusText(r.statusCode))); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestRequestRedirectHandler(t *testing.T) {
|
||||
|
@ -24,49 +25,107 @@ func TestRequestRedirectHandler(t *testing.T) {
|
|||
expectedStatus int
|
||||
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",
|
||||
config: dynamic.RequestRedirect{
|
||||
Regex: `^(?:http?:\/\/)(foo)(\.com)(:\d+)(.*)$`,
|
||||
Replacement: "https://${1}bar$2:443$4",
|
||||
Scheme: ptr.To("https"),
|
||||
Hostname: ptr.To("foobar.com"),
|
||||
Port: ptr.To("443"),
|
||||
},
|
||||
url: "http://foo.com:80",
|
||||
expectedURL: "https://foobar.com:443",
|
||||
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",
|
||||
config: dynamic.RequestRedirect{
|
||||
Regex: `^http://`,
|
||||
Replacement: "https://$1",
|
||||
Permanent: true,
|
||||
Scheme: ptr.To("https"),
|
||||
StatusCode: http.StatusMovedPermanently,
|
||||
},
|
||||
url: "http://foo",
|
||||
expectedURL: "https://foo",
|
||||
|
@ -75,9 +134,8 @@ func TestRequestRedirectHandler(t *testing.T) {
|
|||
{
|
||||
desc: "HTTPS to HTTP permanent",
|
||||
config: dynamic.RequestRedirect{
|
||||
Regex: `https://foo`,
|
||||
Replacement: "http://foo",
|
||||
Permanent: true,
|
||||
Scheme: ptr.To("http"),
|
||||
StatusCode: http.StatusMovedPermanently,
|
||||
},
|
||||
secured: true,
|
||||
url: "https://foo",
|
||||
|
@ -87,8 +145,8 @@ func TestRequestRedirectHandler(t *testing.T) {
|
|||
{
|
||||
desc: "HTTP to HTTPS",
|
||||
config: dynamic.RequestRedirect{
|
||||
Regex: `http://foo:80`,
|
||||
Replacement: "https://foo:443",
|
||||
Scheme: ptr.To("https"),
|
||||
Port: ptr.To("443"),
|
||||
},
|
||||
url: "http://foo:80",
|
||||
expectedURL: "https://foo:443",
|
||||
|
@ -97,8 +155,8 @@ func TestRequestRedirectHandler(t *testing.T) {
|
|||
{
|
||||
desc: "HTTP to HTTPS, with X-Forwarded-Proto",
|
||||
config: dynamic.RequestRedirect{
|
||||
Regex: `http://foo:80`,
|
||||
Replacement: "https://foo:443",
|
||||
Scheme: ptr.To("https"),
|
||||
Port: ptr.To("443"),
|
||||
},
|
||||
url: "http://foo:80",
|
||||
headers: map[string]string{
|
||||
|
@ -110,8 +168,8 @@ func TestRequestRedirectHandler(t *testing.T) {
|
|||
{
|
||||
desc: "HTTPS to HTTP",
|
||||
config: dynamic.RequestRedirect{
|
||||
Regex: `https://foo:443`,
|
||||
Replacement: "http://foo:80",
|
||||
Scheme: ptr.To("http"),
|
||||
Port: ptr.To("80"),
|
||||
},
|
||||
secured: true,
|
||||
url: "https://foo:443",
|
||||
|
@ -121,36 +179,13 @@ func TestRequestRedirectHandler(t *testing.T) {
|
|||
{
|
||||
desc: "HTTP to HTTP",
|
||||
config: dynamic.RequestRedirect{
|
||||
Regex: `http://foo:80`,
|
||||
Replacement: "http://foo:88",
|
||||
Scheme: ptr.To("http"),
|
||||
Port: ptr.To("88"),
|
||||
},
|
||||
url: "http://foo:80",
|
||||
expectedURL: "http://foo:88",
|
||||
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 {
|
||||
|
@ -188,7 +223,7 @@ func TestRequestRedirectHandler(t *testing.T) {
|
|||
|
||||
assert.Equal(t, test.expectedStatus, recorder.Code)
|
||||
switch test.expectedStatus {
|
||||
case http.StatusMovedPermanently, http.StatusFound, http.StatusTemporaryRedirect, http.StatusPermanentRedirect:
|
||||
case http.StatusMovedPermanently, http.StatusFound:
|
||||
location, err := recorder.Result().Location()
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
|
@ -39,7 +39,11 @@ spec:
|
|||
hostnames:
|
||||
- "example.org"
|
||||
rules:
|
||||
- filters:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
filters:
|
||||
- type: RequestRedirect
|
||||
requestRedirect:
|
||||
scheme: https
|
||||
|
|
|
@ -39,7 +39,11 @@ spec:
|
|||
hostnames:
|
||||
- "example.org"
|
||||
rules:
|
||||
- filters:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
filters:
|
||||
- type: RequestRedirect
|
||||
requestRedirect:
|
||||
hostname: example.com
|
||||
|
|
|
@ -39,7 +39,11 @@ spec:
|
|||
hostnames:
|
||||
- "example.org"
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -37,7 +37,11 @@ spec:
|
|||
- "foo.com"
|
||||
- "bar.com"
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -107,7 +107,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
@ -131,7 +135,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
|
@ -151,7 +159,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
|
|
|
@ -33,7 +33,11 @@ metadata:
|
|||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -33,7 +33,11 @@ metadata:
|
|||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -89,7 +89,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -33,7 +33,11 @@ metadata:
|
|||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -119,7 +119,11 @@ metadata:
|
|||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
@ -134,7 +138,11 @@ metadata:
|
|||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -107,7 +107,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
@ -177,7 +181,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -107,7 +107,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
@ -177,7 +181,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -130,7 +130,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
@ -200,7 +204,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -53,7 +53,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -38,7 +38,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -53,7 +53,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -54,7 +54,11 @@ spec:
|
|||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami-bar
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -37,7 +37,11 @@ spec:
|
|||
- "foo.com"
|
||||
- "*.bar.com"
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -37,7 +37,11 @@ spec:
|
|||
- "foo.com"
|
||||
- "*.foo.com"
|
||||
rules:
|
||||
- backendRefs:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
|
|
@ -20,8 +20,8 @@ import (
|
|||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
func (p *Provider) loadHTTPRoutes(ctx context.Context, client Client, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
||||
routes, err := client.ListHTTPRoutes()
|
||||
func (p *Provider) loadHTTPRoutes(ctx context.Context, gatewayListeners []gatewayListener, conf *dynamic.Configuration) {
|
||||
routes, err := p.client.ListHTTPRoutes()
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Unable to list HTTPRoutes")
|
||||
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 {
|
||||
mergeHTTPConfiguration(routeConf, conf)
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ func (p *Provider) loadHTTPRoutes(ctx context.Context, client Client, gatewayLis
|
|||
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().
|
||||
Err(err).
|
||||
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) {
|
||||
routeConf := &dynamic.Configuration{
|
||||
func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener, route *gatev1.HTTPRoute, hostnames []gatev1.Hostname) (*dynamic.Configuration, metav1.Condition) {
|
||||
conf := &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: make(map[string]*dynamic.Router),
|
||||
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),
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: route.Generation,
|
||||
|
@ -116,95 +116,101 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, client Client, listener ga
|
|||
Reason: string(gatev1.RouteConditionResolvedRefs),
|
||||
}
|
||||
|
||||
for _, routeRule := range route.Spec.Rules {
|
||||
rule, priority := buildRouterRule(hostnames, routeRule.Matches)
|
||||
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{
|
||||
errWrr := dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "invalid-httproute-filter",
|
||||
Status: ptr.To(500),
|
||||
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.
|
||||
// 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")
|
||||
|
||||
group := groupCore
|
||||
|
@ -217,9 +223,9 @@ func (p *Provider) loadHTTPService(client Client, route *gatev1.HTTPRoute, backe
|
|||
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{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
|
@ -261,7 +267,7 @@ func (p *Provider) loadHTTPService(client Client, route *gatev1.HTTPRoute, backe
|
|||
portStr := strconv.FormatInt(int64(port), 10)
|
||||
serviceName = provider.Normalize(serviceName + "-" + portStr)
|
||||
|
||||
lb, err := loadHTTPServers(client, namespace, backendRef)
|
||||
lb, err := p.loadHTTPServers(namespace, backendRef)
|
||||
if err != nil {
|
||||
return serviceName, nil, &metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
|
@ -294,18 +300,17 @@ func (p *Provider) loadHTTPBackendRef(namespace string, backendRef gatev1.HTTPBa
|
|||
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)
|
||||
|
||||
for i, filter := range filters {
|
||||
switch filter.Type {
|
||||
case gatev1.HTTPRouteFilterRequestRedirect:
|
||||
middlewareName := provider.Normalize(fmt.Sprintf("%s-%s-%d", prefix, strings.ToLower(string(filter.Type)), i))
|
||||
middlewares[middlewareName] = createRedirectMiddleware(filter.RequestRedirect)
|
||||
name := fmt.Sprintf("%s-%s-%d", routerName, strings.ToLower(string(filter.Type)), i)
|
||||
middlewares[name] = createRequestRedirect(filter.RequestRedirect, pathMatch)
|
||||
|
||||
case gatev1.HTTPRouteFilterRequestHeaderModifier:
|
||||
middlewareName := provider.Normalize(fmt.Sprintf("%s-%s-%d", prefix, strings.ToLower(string(filter.Type)), i))
|
||||
middlewares[middlewareName] = createRequestHeaderModifier(filter.RequestHeaderModifier)
|
||||
name := fmt.Sprintf("%s-%s-%d", routerName, strings.ToLower(string(filter.Type)), i)
|
||||
middlewares[name] = createRequestHeaderModifier(filter.RequestHeaderModifier)
|
||||
|
||||
case gatev1.HTTPRouteFilterExtensionRef:
|
||||
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) {
|
||||
|
@ -343,9 +357,8 @@ func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRe
|
|||
return filterFunc(string(extensionRef.Name), namespace)
|
||||
}
|
||||
|
||||
// TODO support cross namespace through ReferencePolicy.
|
||||
func loadHTTPServers(client Client, namespace string, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, error) {
|
||||
service, exists, err := client.GetService(namespace, string(backendRef.Name))
|
||||
func (p *Provider) loadHTTPServers(namespace string, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, error) {
|
||||
service, exists, err := p.client.GetService(namespace, string(backendRef.Name))
|
||||
if err != nil {
|
||||
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")
|
||||
}
|
||||
|
||||
endpoints, endpointsExists, err := client.GetEndpoints(namespace, string(backendRef.Name))
|
||||
endpoints, endpointsExists, err := p.client.GetEndpoints(namespace, string(backendRef.Name))
|
||||
if err != nil {
|
||||
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 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)
|
||||
//
|
||||
// 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) {
|
||||
var matchesRules []string
|
||||
var maxPriority int
|
||||
func buildMatchRule(hostnames []gatev1.Hostname, match gatev1.HTTPRouteMatch) (string, int) {
|
||||
path := ptr.Deref(match.Path, gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
})
|
||||
|
||||
for _, match := range routeMatches {
|
||||
path := ptr.Deref(match.Path, gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
})
|
||||
var priority int
|
||||
var matchRules []string
|
||||
|
||||
var priority int
|
||||
var matchRules []string
|
||||
pathRule, pathPriority := buildPathRule(path)
|
||||
matchRules = append(matchRules, pathRule)
|
||||
priority += pathPriority
|
||||
|
||||
pathRule, pathPriority := buildPathRule(path)
|
||||
matchRules = append(matchRules, pathRule)
|
||||
priority += pathPriority
|
||||
headerRules, headersPriority := buildHeaderRules(match.Headers)
|
||||
matchRules = append(matchRules, headerRules...)
|
||||
priority += headersPriority
|
||||
|
||||
headerRules, headersPriority := buildHeaderRules(match.Headers)
|
||||
matchRules = append(matchRules, headerRules...)
|
||||
priority += headersPriority
|
||||
|
||||
matchesRules = append(matchesRules, strings.Join(matchRules, " && "))
|
||||
|
||||
if priority > maxPriority {
|
||||
maxPriority = priority
|
||||
}
|
||||
}
|
||||
matchRulesStr := strings.Join(matchRules, " && ")
|
||||
|
||||
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 == "" {
|
||||
return matchesRulesStr, maxPriority
|
||||
return matchRulesStr, priority
|
||||
}
|
||||
|
||||
// A route with a host should match over the same route with no host.
|
||||
maxPriority += hostPriority
|
||||
return hostRule + " && " + "(" + matchesRulesStr + ")", maxPriority
|
||||
priority += hostPriority
|
||||
return hostRule + " && " + matchRulesStr, priority
|
||||
}
|
||||
|
||||
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 {
|
||||
filterScheme := ptr.Deref(filter.Scheme, "${scheme}")
|
||||
func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch *gatev1.HTTPPathMatch) *dynamic.Middleware {
|
||||
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" {
|
||||
port = ""
|
||||
port = ptr.To("")
|
||||
}
|
||||
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)
|
||||
|
||||
hostname := "${hostname}"
|
||||
if filter.Hostname != nil && *filter.Hostname != "" {
|
||||
hostname = string(*filter.Hostname)
|
||||
var path *string
|
||||
var pathPrefix *string
|
||||
if filter.Path != nil {
|
||||
switch filter.Path.Type {
|
||||
case gatev1.FullPathHTTPPathModifier:
|
||||
path = filter.Path.ReplaceFullPath
|
||||
case gatev1.PrefixMatchHTTPPathModifier:
|
||||
path = filter.Path.ReplacePrefixMatch
|
||||
pathPrefix = pathMatch.Value
|
||||
}
|
||||
}
|
||||
|
||||
return &dynamic.Middleware{
|
||||
RequestRedirect: &dynamic.RequestRedirect{
|
||||
Regex: `^(?P<scheme>[a-z]+):\/\/(?P<userinfo>.+@)?(?P<hostname>\[[\w:\.]+\]|[\w\._-]+)(?P<port>:\d+)?\/(?P<path>.*)`,
|
||||
Replacement: fmt.Sprintf("%s://${userinfo}%s%s/${path}", filterScheme, hostname, port),
|
||||
Permanent: statusCode == http.StatusMovedPermanently,
|
||||
Scheme: filter.Scheme,
|
||||
Hostname: hostname,
|
||||
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 {
|
||||
desc string
|
||||
routeMatches []gatev1.HTTPRouteMatch
|
||||
routeMatch gatev1.HTTPRouteMatch
|
||||
hostnames []gatev1.Hostname
|
||||
expectedRule string
|
||||
expectedPriority int
|
||||
|
@ -84,187 +84,112 @@ func Test_buildRouterRule(t *testing.T) {
|
|||
expectedPriority: 1,
|
||||
},
|
||||
{
|
||||
desc: "One Host rule without matches",
|
||||
desc: "One Host rule without match",
|
||||
hostnames: []gatev1.Hostname{"foo.com"},
|
||||
expectedRule: "Host(`foo.com`)",
|
||||
expectedPriority: 7,
|
||||
expectedRule: "Host(`foo.com`) && PathPrefix(`/`)",
|
||||
expectedPriority: 8,
|
||||
},
|
||||
{
|
||||
desc: "One HTTPRouteMatch with nil HTTPHeaderMatch",
|
||||
routeMatches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: ptr.To(gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
}),
|
||||
Headers: nil,
|
||||
},
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: ptr.To(gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
}),
|
||||
Headers: nil,
|
||||
},
|
||||
expectedRule: "PathPrefix(`/`)",
|
||||
expectedPriority: 1,
|
||||
},
|
||||
{
|
||||
desc: "One HTTPRouteMatch with nil HTTPHeaderMatch Type",
|
||||
routeMatches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: ptr.To(gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
}),
|
||||
Headers: []gatev1.HTTPHeaderMatch{
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: ptr.To(gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
}),
|
||||
Headers: []gatev1.HTTPHeaderMatch{
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
expectedRule: "PathPrefix(`/`) && Header(`foo`,`bar`)",
|
||||
expectedPriority: 91,
|
||||
expectedPriority: 101,
|
||||
},
|
||||
{
|
||||
desc: "One HTTPRouteMatch with nil HTTPPathMatch",
|
||||
routeMatches: []gatev1.HTTPRouteMatch{
|
||||
{Path: nil},
|
||||
},
|
||||
desc: "One HTTPRouteMatch with nil HTTPPathMatch",
|
||||
routeMatch: gatev1.HTTPRouteMatch{Path: nil},
|
||||
expectedRule: "PathPrefix(`/`)",
|
||||
expectedPriority: 1,
|
||||
},
|
||||
{
|
||||
desc: "One HTTPRouteMatch with nil HTTPPathMatch Type",
|
||||
routeMatches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: nil,
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: nil,
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
},
|
||||
expectedRule: "(Path(`/foo`) || PathPrefix(`/foo/`))",
|
||||
expectedPriority: 10490,
|
||||
expectedPriority: 10500,
|
||||
},
|
||||
{
|
||||
desc: "One HTTPRouteMatch with nil HTTPPathMatch Values",
|
||||
routeMatches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: nil,
|
||||
},
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: nil,
|
||||
},
|
||||
},
|
||||
expectedRule: "Path(`/`)",
|
||||
expectedPriority: 99990,
|
||||
expectedPriority: 100000,
|
||||
},
|
||||
{
|
||||
desc: "One Path in matches",
|
||||
routeMatches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
desc: "One Path",
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
},
|
||||
expectedRule: "Path(`/foo/`)",
|
||||
expectedPriority: 99990,
|
||||
expectedPriority: 100000,
|
||||
},
|
||||
{
|
||||
desc: "One Path in matches and another empty",
|
||||
routeMatches: []gatev1.HTTPRouteMatch{
|
||||
{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
desc: "Path && Header",
|
||||
routeMatch: gatev1.HTTPRouteMatch{
|
||||
Path: &gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchExact),
|
||||
Value: ptr.To("/foo/"),
|
||||
},
|
||||
{},
|
||||
},
|
||||
expectedRule: "Path(`/foo/`) || PathPrefix(`/`)",
|
||||
expectedPriority: 99980,
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
Headers: []gatev1.HTTPHeaderMatch{
|
||||
{
|
||||
Type: ptr.To(gatev1.HeaderMatchExact),
|
||||
Name: "my-header",
|
||||
Value: "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"},
|
||||
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",
|
||||
},
|
||||
routeMatch: 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/`) && Header(`my-header`,`foo`))",
|
||||
expectedPriority: 100097,
|
||||
},
|
||||
{
|
||||
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,
|
||||
expectedRule: "Host(`foo.com`) && Path(`/foo/`) && Header(`my-header`,`foo`)",
|
||||
expectedPriority: 100107,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -272,7 +197,7 @@ func Test_buildRouterRule(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
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.expectedPriority, priority)
|
||||
})
|
||||
|
|
|
@ -68,6 +68,7 @@ type Provider struct {
|
|||
lastConfiguration safe.Safe
|
||||
|
||||
routerTransform k8s.RouterTransform
|
||||
client *clientWrapper
|
||||
}
|
||||
|
||||
// Entrypoint defines the available entry points.
|
||||
|
@ -194,6 +195,14 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
|
|||
|
||||
// Init the provider.
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -202,14 +211,9 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
|||
logger := log.With().Str(logs.ProviderName, providerName).Logger()
|
||||
ctxLog := logger.WithContext(context.Background())
|
||||
|
||||
k8sClient, err := p.newK8sClient(ctxLog)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pool.GoCtx(func(ctxPool context.Context) {
|
||||
operation := func() error {
|
||||
eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done())
|
||||
eventsChan, err := p.client.WatchAll(p.Namespaces, ctxPool.Done())
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Error watching kubernetes events")
|
||||
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.
|
||||
// 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.
|
||||
conf := p.loadConfigurationFromGateways(ctxLog, k8sClient)
|
||||
conf := p.loadConfigurationFromGateways(ctxLog)
|
||||
|
||||
confHash, err := hashstructure.Hash(conf, nil)
|
||||
switch {
|
||||
|
@ -272,7 +276,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
|||
}
|
||||
|
||||
// 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{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
|
@ -293,13 +297,13 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
|||
TLS: &dynamic.TLSConfiguration{},
|
||||
}
|
||||
|
||||
addresses, err := p.gatewayAddresses(client)
|
||||
addresses, err := p.gatewayAddresses()
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Unable to get Gateway status addresses")
|
||||
return nil
|
||||
}
|
||||
|
||||
gatewayClasses, err := client.ListGatewayClasses()
|
||||
gatewayClasses, err := p.client.ListGatewayClasses()
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Unable to list GatewayClasses")
|
||||
return nil
|
||||
|
@ -313,7 +317,7 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
|||
|
||||
gatewayClassNames[gatewayClass.Name] = struct{}{}
|
||||
|
||||
err := client.UpdateGatewayClassStatus(gatewayClass, metav1.Condition{
|
||||
err := p.client.UpdateGatewayClassStatus(gatewayClass, metav1.Condition{
|
||||
Type: string(gatev1.GatewayClassConditionStatusAccepted),
|
||||
Status: metav1.ConditionTrue,
|
||||
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
|
||||
for _, gateway := range gateways {
|
||||
|
@ -343,14 +347,14 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
|||
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 {
|
||||
p.loadTCPRoutes(ctx, client, gatewayListeners, conf)
|
||||
p.loadTLSRoutes(ctx, client, gatewayListeners, conf)
|
||||
p.loadTCPRoutes(ctx, gatewayListeners, conf)
|
||||
p.loadTLSRoutes(ctx, gatewayListeners, conf)
|
||||
}
|
||||
|
||||
for _, gateway := range gateways {
|
||||
|
@ -367,7 +371,7 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
|||
}
|
||||
|
||||
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().
|
||||
Err(err).
|
||||
Msg("Unable to update Gateway status")
|
||||
|
@ -382,7 +386,7 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context, client Cli
|
|||
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)
|
||||
allocatedListeners := make(map[string]struct{})
|
||||
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
|
||||
|
||||
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 {
|
||||
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
|
||||
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)
|
||||
}
|
||||
|
||||
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{
|
||||
Type: string(gatev1.ListenerConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
|
@ -580,7 +584,7 @@ func (p *Provider) loadGatewayListeners(ctx context.Context, client Client, gate
|
|||
|
||||
configKey := certificateNamespace + "/" + string(certificateRef.Name)
|
||||
if _, tlsExists := tlsConfigs[configKey]; !tlsExists {
|
||||
tlsConf, err := getTLS(client, certificateRef.Name, certificateNamespace)
|
||||
tlsConf, err := p.getTLS(certificateRef.Name, certificateNamespace)
|
||||
if err != nil {
|
||||
// update "ResolvedRefs" status false with "InvalidCertificateRef" reason
|
||||
// update "Programmed" status false with "Invalid" reason
|
||||
|
@ -703,7 +707,7 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listeners []gatewa
|
|||
return gatewayStatus, nil
|
||||
}
|
||||
|
||||
func (p *Provider) gatewayAddresses(client Client) ([]gatev1.GatewayStatusAddress, error) {
|
||||
func (p *Provider) gatewayAddresses() ([]gatev1.GatewayStatusAddress, error) {
|
||||
if p.StatusAddress == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -724,7 +728,7 @@ func (p *Provider) gatewayAddresses(client Client) ([]gatev1.GatewayStatusAddres
|
|||
|
||||
svcRef := p.StatusAddress.Service
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
group := gatev1.Group(gatev1.GroupName)
|
||||
|
||||
|
@ -856,30 +925,6 @@ func allowedRouteKinds(gateway *gatev1.Gateway, listener gatev1.Listener, suppor
|
|||
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) {
|
||||
if listenerHostname == nil {
|
||||
return routeHostnames, true
|
||||
|
@ -971,7 +1016,7 @@ func matchListener(listener gatewayListener, routeNamespace string, parentRef ga
|
|||
return true
|
||||
}
|
||||
|
||||
func makeRouterKey(rule, name string) string {
|
||||
func makeRouterName(rule, name string) string {
|
||||
h := sha256.New()
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
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 {
|
||||
var secretNames []string
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
var matchingReferenceGrants []*gatev1beta1.ReferenceGrant
|
||||
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"
|
||||
)
|
||||
|
||||
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)
|
||||
routes, err := client.ListTCPRoutes()
|
||||
routes, err := p.client.ListTCPRoutes()
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msgf("Unable to list TCPRoutes")
|
||||
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 {
|
||||
mergeTCPConfiguration(routeConf, conf)
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, client Client, gatewayList
|
|||
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().
|
||||
Err(err).
|
||||
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) {
|
||||
routeConf := &dynamic.Configuration{
|
||||
func (p *Provider) loadTCPRoute(listener gatewayListener, route *gatev1alpha2.TCPRoute) (*dynamic.Configuration, metav1.Condition) {
|
||||
conf := &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
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),
|
||||
Status: metav1.ConditionTrue,
|
||||
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.
|
||||
routerName := route.Name + "-" + listener.GWName + "-" + listener.EPName
|
||||
routerKey := provider.Normalize(makeRouterKey("", makeID(route.Namespace, routerName)))
|
||||
routerName := provider.Normalize(route.Namespace + "-" + route.Name + "-" + listener.GWName + "-" + listener.EPName)
|
||||
|
||||
var ruleServiceNames []string
|
||||
for i, rule := range route.Spec.Rules {
|
||||
|
@ -130,9 +129,9 @@ func (p *Provider) loadTCPRoute(client Client, listener gatewayListener, route *
|
|||
continue
|
||||
}
|
||||
|
||||
wrrService, subServices, err := loadTCPServices(client, route.Namespace, rule.BackendRefs)
|
||||
wrrService, subServices, err := p.loadTCPServices(route.Namespace, rule.BackendRefs)
|
||||
if err != nil {
|
||||
return routeConf, metav1.Condition{
|
||||
return conf, metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: route.Generation,
|
||||
|
@ -143,22 +142,22 @@ func (p *Provider) loadTCPRoute(client Client, listener gatewayListener, route *
|
|||
}
|
||||
|
||||
for svcName, svc := range subServices {
|
||||
routeConf.TCP.Services[svcName] = svc
|
||||
conf.TCP.Services[svcName] = svc
|
||||
}
|
||||
|
||||
serviceName := fmt.Sprintf("%s-wrr-%d", routerKey, i)
|
||||
routeConf.TCP.Services[serviceName] = wrrService
|
||||
serviceName := fmt.Sprintf("%s-wrr-%d", routerName, i)
|
||||
conf.TCP.Services[serviceName] = wrrService
|
||||
|
||||
ruleServiceNames = append(ruleServiceNames, serviceName)
|
||||
}
|
||||
|
||||
if len(ruleServiceNames) == 1 {
|
||||
router.Service = ruleServiceNames[0]
|
||||
routeConf.TCP.Routers[routerKey] = &router
|
||||
return routeConf, routeCondition
|
||||
conf.TCP.Routers[routerName] = &router
|
||||
return conf, condition
|
||||
}
|
||||
|
||||
routeServiceKey := routerKey + "-wrr"
|
||||
routeServiceKey := routerName + "-wrr"
|
||||
routeService := &dynamic.TCPService{Weighted: &dynamic.TCPWeightedRoundRobin{}}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
routeConf.TCP.Services[routeServiceKey] = routeService
|
||||
conf.TCP.Services[routeServiceKey] = routeService
|
||||
|
||||
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.
|
||||
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{}
|
||||
|
||||
wrrSvc := &dynamic.TCPService{
|
||||
|
@ -211,7 +210,7 @@ func loadTCPServices(client Client, namespace string, backendRefs []gatev1.Backe
|
|||
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 {
|
||||
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")
|
||||
}
|
||||
|
||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, string(backendRef.Name))
|
||||
endpoints, endpointsExists, endpointsErr := p.client.GetEndpoints(namespace, string(backendRef.Name))
|
||||
if endpointsErr != nil {
|
||||
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
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
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)
|
||||
routes, err := client.ListTLSRoutes()
|
||||
routes, err := p.client.ListTLSRoutes()
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msgf("Unable to list TLSRoute")
|
||||
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 {
|
||||
mergeTCPConfiguration(routeConf, conf)
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, client Client, gatewayList
|
|||
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().
|
||||
Err(err).
|
||||
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) {
|
||||
routeConf := &dynamic.Configuration{
|
||||
func (p *Provider) loadTLSRoute(listener gatewayListener, route *gatev1alpha2.TLSRoute, hostnames []gatev1.Hostname) (*dynamic.Configuration, metav1.Condition) {
|
||||
conf := &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
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),
|
||||
Status: metav1.ConditionTrue,
|
||||
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.
|
||||
routerName := route.Name + "-" + listener.GWName + "-" + listener.EPName
|
||||
routerKey := provider.Normalize(makeRouterKey(router.Rule, makeID(route.Namespace, routerName)))
|
||||
routeKey := provider.Normalize(route.Namespace + "-" + route.Name + "-" + listener.GWName + "-" + listener.EPName)
|
||||
routerName := makeRouterName(router.Rule, routeKey)
|
||||
|
||||
var ruleServiceNames []string
|
||||
for i, routeRule := range route.Spec.Rules {
|
||||
|
@ -128,10 +128,10 @@ func (p *Provider) loadTLSRoute(client Client, listener gatewayListener, route *
|
|||
continue
|
||||
}
|
||||
|
||||
wrrService, subServices, err := loadTCPServices(client, route.Namespace, routeRule.BackendRefs)
|
||||
wrrService, subServices, err := p.loadTCPServices(route.Namespace, routeRule.BackendRefs)
|
||||
if err != nil {
|
||||
// update "ResolvedRefs" status true with "InvalidBackendRefs" reason
|
||||
routeCondition = metav1.Condition{
|
||||
condition = metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: route.Generation,
|
||||
|
@ -143,23 +143,23 @@ func (p *Provider) loadTLSRoute(client Client, listener gatewayListener, route *
|
|||
}
|
||||
|
||||
for svcName, svc := range subServices {
|
||||
routeConf.TCP.Services[svcName] = svc
|
||||
conf.TCP.Services[svcName] = svc
|
||||
}
|
||||
|
||||
serviceName := fmt.Sprintf("%s-wrr-%d", routerKey, i)
|
||||
routeConf.TCP.Services[serviceName] = wrrService
|
||||
serviceName := fmt.Sprintf("%s-wrr-%d", routerName, i)
|
||||
conf.TCP.Services[serviceName] = wrrService
|
||||
|
||||
ruleServiceNames = append(ruleServiceNames, serviceName)
|
||||
}
|
||||
|
||||
if len(ruleServiceNames) == 1 {
|
||||
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{}}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
routeConf.TCP.Services[routeServiceKey] = routeService
|
||||
conf.TCP.Services[routeServiceKey] = routeService
|
||||
|
||||
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 {
|
||||
|
|
Loading…
Reference in a new issue