mirror of
https://github.com/fluxcd/flagger.git
synced 2026-02-14 18:10:00 +00:00
gatewayapi: add support for route rule filters
Add support for [`Filters`](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilter) in the HTTPRoute API. We reuse most of the existing fields used for Istio to construct the appopriate filter. A new API `.spec.service.mirror` is added to allow for request mirroring. The `.spec.service.rewrite` API has been changed to a custom `HTTPRewrite` API instead of importing it from Istio, to allow covering all features that Gateway API provides. Support for the [`RequestRedirect`](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRequestRedirectFilter) Filter has been left out on purpose, since it's not possible to specify it if the same rule also specifies `.backendRefs` (which Flagger does). Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
This commit is contained in:
@@ -482,6 +482,54 @@ spec:
|
||||
uri:
|
||||
format: string
|
||||
type: string
|
||||
authority:
|
||||
format: string
|
||||
type: string
|
||||
type:
|
||||
format: string
|
||||
type: string
|
||||
mirror:
|
||||
description: Mirror defines a schema for a filter that mirrors requests.
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
backendRef:
|
||||
properties:
|
||||
group:
|
||||
default: ""
|
||||
maxLength: 253
|
||||
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
|
||||
type: string
|
||||
kind:
|
||||
default: Service
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
|
||||
type: string
|
||||
name:
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
type: string
|
||||
namespace:
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
|
||||
type: string
|
||||
port:
|
||||
format: int32
|
||||
maximum: 65535
|
||||
minimum: 1
|
||||
type: integer
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: Must have port for Service reference
|
||||
rule: '(size(self.group) == 0 && self.kind == ''Service'')
|
||||
? has(self.port) : true'
|
||||
required:
|
||||
- backendRef
|
||||
headers:
|
||||
description: Headers operations
|
||||
type: object
|
||||
|
||||
@@ -482,6 +482,54 @@ spec:
|
||||
uri:
|
||||
format: string
|
||||
type: string
|
||||
authority:
|
||||
format: string
|
||||
type: string
|
||||
type:
|
||||
format: string
|
||||
type: string
|
||||
mirror:
|
||||
description: Mirror defines a schema for a filter that mirrors requests.
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
backendRef:
|
||||
properties:
|
||||
group:
|
||||
default: ""
|
||||
maxLength: 253
|
||||
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
|
||||
type: string
|
||||
kind:
|
||||
default: Service
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
|
||||
type: string
|
||||
name:
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
type: string
|
||||
namespace:
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
|
||||
type: string
|
||||
port:
|
||||
format: int32
|
||||
maximum: 65535
|
||||
minimum: 1
|
||||
type: integer
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: Must have port for Service reference
|
||||
rule: '(size(self.group) == 0 && self.kind == ''Service'')
|
||||
? has(self.port) : true'
|
||||
required:
|
||||
- backendRef
|
||||
headers:
|
||||
description: Headers operations
|
||||
type: object
|
||||
|
||||
@@ -482,6 +482,54 @@ spec:
|
||||
uri:
|
||||
format: string
|
||||
type: string
|
||||
authority:
|
||||
format: string
|
||||
type: string
|
||||
type:
|
||||
format: string
|
||||
type: string
|
||||
mirror:
|
||||
description: Mirror defines a schema for a filter that mirrors requests.
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
backendRef:
|
||||
properties:
|
||||
group:
|
||||
default: ""
|
||||
maxLength: 253
|
||||
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
|
||||
type: string
|
||||
kind:
|
||||
default: Service
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
|
||||
type: string
|
||||
name:
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
type: string
|
||||
namespace:
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
|
||||
type: string
|
||||
port:
|
||||
format: int32
|
||||
maximum: 65535
|
||||
minimum: 1
|
||||
type: integer
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: Must have port for Service reference
|
||||
rule: '(size(self.group) == 0 && self.kind == ''Service'')
|
||||
? has(self.port) : true'
|
||||
required:
|
||||
- backendRef
|
||||
headers:
|
||||
description: Headers operations
|
||||
type: object
|
||||
|
||||
@@ -181,7 +181,7 @@ type CanaryService struct {
|
||||
|
||||
// Rewrite HTTP URIs for the generated service
|
||||
// +optional
|
||||
Rewrite *istiov1alpha3.HTTPRewrite `json:"rewrite,omitempty"`
|
||||
Rewrite *HTTPRewrite `json:"rewrite,omitempty"`
|
||||
|
||||
// Retries policy for the generated virtual service
|
||||
// +optional
|
||||
@@ -191,6 +191,10 @@ type CanaryService struct {
|
||||
// +optional
|
||||
Headers *istiov1alpha3.Headers `json:"headers,omitempty"`
|
||||
|
||||
// Mirror specifies the destination for request mirroring.
|
||||
// Responses from this destination are dropped.
|
||||
Mirror []v1beta1.HTTPRequestMirrorFilter `json:"mirror,omitempty"`
|
||||
|
||||
// Cross-Origin Resource Sharing policy for the generated Istio virtual service
|
||||
// +optional
|
||||
CorsPolicy *istiov1alpha3.CorsPolicy `json:"corsPolicy,omitempty"`
|
||||
@@ -484,6 +488,41 @@ type CustomMetadata struct {
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// HTTPRewrite holds information about how to modify a request URI during
|
||||
// forwarding.
|
||||
type HTTPRewrite struct {
|
||||
// rewrite the path (or the prefix) portion of the URI with this
|
||||
// value. If the original URI was matched based on prefix, the value
|
||||
// provided in this field will replace the corresponding matched prefix.
|
||||
Uri string `json:"uri,omitempty"`
|
||||
|
||||
// rewrite the Authority/Host header with this value.
|
||||
Authority string `json:"authority,omitempty"`
|
||||
|
||||
// Type is the type of path modification to make.
|
||||
// +optional
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// GetType returns the type of HTTP path rewrite to be performed.
|
||||
func (r *HTTPRewrite) GetType() string {
|
||||
if r.Type == string(v1beta1.PrefixMatchHTTPPathModifier) {
|
||||
return r.Type
|
||||
}
|
||||
return string(v1beta1.FullPathHTTPPathModifier)
|
||||
}
|
||||
|
||||
// GetIstioRewrite returns a istiov1alpha3.HTTPRewrite object.
|
||||
func (s *CanaryService) GetIstioRewrite() *istiov1alpha3.HTTPRewrite {
|
||||
if s.Rewrite != nil {
|
||||
return &istiov1alpha3.HTTPRewrite{
|
||||
Authority: s.Rewrite.Authority,
|
||||
Uri: s.Rewrite.Uri,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMaxAge returns the max age of a cookie in seconds.
|
||||
func (s *SessionAffinity) GetMaxAge() int {
|
||||
if s.MaxAge == 0 {
|
||||
|
||||
@@ -405,7 +405,7 @@ func (in *CanaryService) DeepCopyInto(out *CanaryService) {
|
||||
}
|
||||
if in.Rewrite != nil {
|
||||
in, out := &in.Rewrite, &out.Rewrite
|
||||
*out = new(v1alpha3.HTTPRewrite)
|
||||
*out = new(HTTPRewrite)
|
||||
**out = **in
|
||||
}
|
||||
if in.Retries != nil {
|
||||
@@ -418,6 +418,13 @@ func (in *CanaryService) DeepCopyInto(out *CanaryService) {
|
||||
*out = new(v1alpha3.Headers)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Mirror != nil {
|
||||
in, out := &in.Mirror, &out.Mirror
|
||||
*out = make([]gatewayapiv1beta1.HTTPRequestMirrorFilter, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.CorsPolicy != nil {
|
||||
in, out := &in.CorsPolicy, &out.CorsPolicy
|
||||
*out = new(v1alpha3.CorsPolicy)
|
||||
@@ -666,6 +673,22 @@ func (in *CustomMetadata) DeepCopy() *CustomMetadata {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HTTPRewrite) DeepCopyInto(out *HTTPRewrite) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRewrite.
|
||||
func (in *HTTPRewrite) DeepCopy() *HTTPRewrite {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HTTPRewrite)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) {
|
||||
*out = *in
|
||||
|
||||
@@ -87,6 +87,7 @@ func (gwr *GatewayAPIV1Beta1Router) Reconcile(canary *flaggerv1.Canary) error {
|
||||
Rules: []v1beta1.HTTPRouteRule{
|
||||
{
|
||||
Matches: matches,
|
||||
Filters: gwr.makeFilters(canary),
|
||||
BackendRefs: []v1beta1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: gwr.makeBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port),
|
||||
@@ -106,6 +107,7 @@ func (gwr *GatewayAPIV1Beta1Router) Reconcile(canary *flaggerv1.Canary) error {
|
||||
httpRouteSpec.Rules[0].Matches = gwr.mergeMatchConditions(analysisMatches, matches)
|
||||
httpRouteSpec.Rules = append(httpRouteSpec.Rules, v1beta1.HTTPRouteRule{
|
||||
Matches: matches,
|
||||
Filters: gwr.makeFilters(canary),
|
||||
BackendRefs: []v1beta1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: gwr.makeBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port),
|
||||
@@ -295,6 +297,7 @@ func (gwr *GatewayAPIV1Beta1Router) SetRoutes(
|
||||
}
|
||||
weightedRouteRule := &v1beta1.HTTPRouteRule{
|
||||
Matches: matches,
|
||||
Filters: gwr.makeFilters(canary),
|
||||
BackendRefs: []v1beta1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: gwr.makeBackendRef(primarySvcName, pWeight, canary.Spec.Service.Port),
|
||||
@@ -330,6 +333,7 @@ func (gwr *GatewayAPIV1Beta1Router) SetRoutes(
|
||||
hrClone.Spec.Rules[0].Matches = gwr.mergeMatchConditions(analysisMatches, matches)
|
||||
hrClone.Spec.Rules = append(hrClone.Spec.Rules, v1beta1.HTTPRouteRule{
|
||||
Matches: matches,
|
||||
Filters: gwr.makeFilters(canary),
|
||||
BackendRefs: []v1beta1.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: gwr.makeBackendRef(primarySvcName, initialPrimaryWeight, canary.Spec.Service.Port),
|
||||
@@ -570,3 +574,94 @@ func (gwr *GatewayAPIV1Beta1Router) mergeMatchConditions(analysis, service []v1b
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func (gwr *GatewayAPIV1Beta1Router) makeFilters(canary *flaggerv1.Canary) []v1beta1.HTTPRouteFilter {
|
||||
var filters []v1beta1.HTTPRouteFilter
|
||||
|
||||
if canary.Spec.Service.Headers != nil {
|
||||
if canary.Spec.Service.Headers.Request != nil {
|
||||
requestHeaderFilter := v1beta1.HTTPRouteFilter{
|
||||
Type: v1beta1.HTTPRouteFilterRequestHeaderModifier,
|
||||
RequestHeaderModifier: &v1beta1.HTTPHeaderFilter{},
|
||||
}
|
||||
|
||||
for name, val := range canary.Spec.Service.Headers.Request.Add {
|
||||
requestHeaderFilter.RequestHeaderModifier.Add = append(requestHeaderFilter.RequestHeaderModifier.Add, v1beta1.HTTPHeader{
|
||||
Name: v1beta1.HTTPHeaderName(name),
|
||||
Value: val,
|
||||
})
|
||||
}
|
||||
for name, val := range canary.Spec.Service.Headers.Request.Set {
|
||||
requestHeaderFilter.RequestHeaderModifier.Set = append(requestHeaderFilter.RequestHeaderModifier.Set, v1beta1.HTTPHeader{
|
||||
Name: v1beta1.HTTPHeaderName(name),
|
||||
Value: val,
|
||||
})
|
||||
}
|
||||
|
||||
for _, name := range canary.Spec.Service.Headers.Request.Remove {
|
||||
requestHeaderFilter.RequestHeaderModifier.Remove = append(requestHeaderFilter.RequestHeaderModifier.Remove, name)
|
||||
}
|
||||
|
||||
filters = append(filters, requestHeaderFilter)
|
||||
}
|
||||
if canary.Spec.Service.Headers.Response != nil {
|
||||
responseHeaderFilter := v1beta1.HTTPRouteFilter{
|
||||
Type: v1beta1.HTTPRouteFilterResponseHeaderModifier,
|
||||
ResponseHeaderModifier: &v1beta1.HTTPHeaderFilter{},
|
||||
}
|
||||
|
||||
for name, val := range canary.Spec.Service.Headers.Response.Add {
|
||||
responseHeaderFilter.ResponseHeaderModifier.Add = append(responseHeaderFilter.ResponseHeaderModifier.Add, v1beta1.HTTPHeader{
|
||||
Name: v1beta1.HTTPHeaderName(name),
|
||||
Value: val,
|
||||
})
|
||||
}
|
||||
for name, val := range canary.Spec.Service.Headers.Response.Set {
|
||||
responseHeaderFilter.ResponseHeaderModifier.Set = append(responseHeaderFilter.ResponseHeaderModifier.Set, v1beta1.HTTPHeader{
|
||||
Name: v1beta1.HTTPHeaderName(name),
|
||||
Value: val,
|
||||
})
|
||||
}
|
||||
|
||||
for _, name := range canary.Spec.Service.Headers.Response.Remove {
|
||||
responseHeaderFilter.ResponseHeaderModifier.Remove = append(responseHeaderFilter.ResponseHeaderModifier.Remove, name)
|
||||
}
|
||||
|
||||
filters = append(filters, responseHeaderFilter)
|
||||
}
|
||||
}
|
||||
|
||||
if canary.Spec.Service.Rewrite != nil {
|
||||
rewriteFilter := v1beta1.HTTPRouteFilter{
|
||||
Type: v1beta1.HTTPRouteFilterURLRewrite,
|
||||
URLRewrite: &v1beta1.HTTPURLRewriteFilter{},
|
||||
}
|
||||
if canary.Spec.Service.Rewrite.Authority != "" {
|
||||
hostname := v1beta1.PreciseHostname(canary.Spec.Service.Rewrite.Authority)
|
||||
rewriteFilter.URLRewrite.Hostname = &hostname
|
||||
}
|
||||
if canary.Spec.Service.Rewrite.Uri != "" {
|
||||
rewriteFilter.URLRewrite.Path = &v1beta1.HTTPPathModifier{
|
||||
Type: v1beta1.HTTPPathModifierType(canary.Spec.Service.Rewrite.GetType()),
|
||||
}
|
||||
if rewriteFilter.URLRewrite.Path.Type == v1beta1.FullPathHTTPPathModifier {
|
||||
rewriteFilter.URLRewrite.Path.ReplaceFullPath = &canary.Spec.Service.Rewrite.Uri
|
||||
} else {
|
||||
rewriteFilter.URLRewrite.Path.ReplacePrefixMatch = &canary.Spec.Service.Rewrite.Uri
|
||||
}
|
||||
}
|
||||
|
||||
filters = append(filters, rewriteFilter)
|
||||
}
|
||||
|
||||
for _, mirror := range canary.Spec.Service.Mirror {
|
||||
mirror := mirror
|
||||
mirrorFilter := v1beta1.HTTPRouteFilter{
|
||||
Type: v1beta1.HTTPRouteFilterRequestMirror,
|
||||
RequestMirror: &mirror,
|
||||
}
|
||||
filters = append(filters, mirrorFilter)
|
||||
}
|
||||
|
||||
return filters
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
|
||||
Http: []istiov1alpha3.HTTPRoute{
|
||||
{
|
||||
Match: canary.Spec.Service.Match,
|
||||
Rewrite: canary.Spec.Service.Rewrite,
|
||||
Rewrite: canary.Spec.Service.GetIstioRewrite(),
|
||||
Timeout: canary.Spec.Service.Timeout,
|
||||
Retries: canary.Spec.Service.Retries,
|
||||
CorsPolicy: canary.Spec.Service.CorsPolicy,
|
||||
@@ -208,7 +208,7 @@ func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
|
||||
newSpec.Http = []istiov1alpha3.HTTPRoute{
|
||||
{
|
||||
Match: canaryMatch,
|
||||
Rewrite: canary.Spec.Service.Rewrite,
|
||||
Rewrite: canary.Spec.Service.GetIstioRewrite(),
|
||||
Timeout: canary.Spec.Service.Timeout,
|
||||
Retries: canary.Spec.Service.Retries,
|
||||
CorsPolicy: canary.Spec.Service.CorsPolicy,
|
||||
@@ -217,7 +217,7 @@ func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
|
||||
},
|
||||
{
|
||||
Match: canary.Spec.Service.Match,
|
||||
Rewrite: canary.Spec.Service.Rewrite,
|
||||
Rewrite: canary.Spec.Service.GetIstioRewrite(),
|
||||
Timeout: canary.Spec.Service.Timeout,
|
||||
Retries: canary.Spec.Service.Retries,
|
||||
CorsPolicy: canary.Spec.Service.CorsPolicy,
|
||||
@@ -415,7 +415,7 @@ func (ir *IstioRouter) SetRoutes(
|
||||
// weighted routing (progressive canary)
|
||||
weightedRoute := istiov1alpha3.HTTPRoute{
|
||||
Match: canary.Spec.Service.Match,
|
||||
Rewrite: canary.Spec.Service.Rewrite,
|
||||
Rewrite: canary.Spec.Service.GetIstioRewrite(),
|
||||
Timeout: canary.Spec.Service.Timeout,
|
||||
Retries: canary.Spec.Service.Retries,
|
||||
CorsPolicy: canary.Spec.Service.CorsPolicy,
|
||||
@@ -530,7 +530,7 @@ func (ir *IstioRouter) SetRoutes(
|
||||
vsCopy.Spec.Http = []istiov1alpha3.HTTPRoute{
|
||||
{
|
||||
Match: canaryMatch,
|
||||
Rewrite: canary.Spec.Service.Rewrite,
|
||||
Rewrite: canary.Spec.Service.GetIstioRewrite(),
|
||||
Timeout: canary.Spec.Service.Timeout,
|
||||
Retries: canary.Spec.Service.Retries,
|
||||
CorsPolicy: canary.Spec.Service.CorsPolicy,
|
||||
@@ -542,7 +542,7 @@ func (ir *IstioRouter) SetRoutes(
|
||||
},
|
||||
{
|
||||
Match: canary.Spec.Service.Match,
|
||||
Rewrite: canary.Spec.Service.Rewrite,
|
||||
Rewrite: canary.Spec.Service.GetIstioRewrite(),
|
||||
Timeout: canary.Spec.Service.Timeout,
|
||||
Retries: canary.Spec.Service.Retries,
|
||||
CorsPolicy: canary.Spec.Service.CorsPolicy,
|
||||
|
||||
@@ -573,7 +573,7 @@ func TestIstioRouter_Finalize(t *testing.T) {
|
||||
Http: []istiov1alpha3.HTTPRoute{
|
||||
{
|
||||
Match: mocks.canary.Spec.Service.Match,
|
||||
Rewrite: mocks.canary.Spec.Service.Rewrite,
|
||||
Rewrite: mocks.canary.Spec.Service.GetIstioRewrite(),
|
||||
Timeout: mocks.canary.Spec.Service.Timeout,
|
||||
Retries: mocks.canary.Spec.Service.Retries,
|
||||
CorsPolicy: mocks.canary.Spec.Service.CorsPolicy,
|
||||
|
||||
Reference in New Issue
Block a user