Support HTTPRoute redirect port and scheme

Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
Romain 2024-06-13 11:16:04 +02:00 committed by GitHub
parent 27af1fb478
commit 3ca667a3d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 1063 additions and 997 deletions

View file

@ -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,
), ),

View file

@ -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": {

View file

@ -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"`
} }

View file

@ -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
} }

View file

@ -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)
} }
} }

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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),
}, },
} }
} }

View file

@ -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)
}) })

View file

@ -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

View file

@ -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})

View file

@ -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 {