From fe9ce18a885abd9eb55e0bdff2196f9e5d1128b0 Mon Sep 17 00:00:00 2001 From: Eguzki Astiz Lezaun Date: Wed, 3 May 2023 18:15:57 +0200 Subject: [PATCH 1/4] rpl v2 CRD --- api/v1beta1/ratelimitpolicy_types.go | 313 -------------- api/v1beta1/zz_generated.deepcopy.go | 408 ------------------ api/v1beta2/groupversion_info.go | 36 ++ api/v1beta2/ratelimitpolicy_types.go | 218 ++++++++++ .../ratelimitpolicy_types_test.go | 2 +- api/v1beta2/zz_generated.deepcopy.go | 226 ++++++++++ config/manager/manager.yaml | 56 +-- 7 files changed, 509 insertions(+), 750 deletions(-) delete mode 100644 api/v1beta1/ratelimitpolicy_types.go create mode 100644 api/v1beta2/groupversion_info.go create mode 100644 api/v1beta2/ratelimitpolicy_types.go rename api/{v1beta1 => v1beta2}/ratelimitpolicy_types_test.go (99%) create mode 100644 api/v1beta2/zz_generated.deepcopy.go diff --git a/api/v1beta1/ratelimitpolicy_types.go b/api/v1beta1/ratelimitpolicy_types.go deleted file mode 100644 index fe12ee53d..000000000 --- a/api/v1beta1/ratelimitpolicy_types.go +++ /dev/null @@ -1,313 +0,0 @@ -/* -Copyright 2021 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1beta1 - -import ( - "fmt" - "reflect" - - "github.com/go-logr/logr" - "github.com/google/go-cmp/cmp" - "github.com/kuadrant/kuadrant-operator/pkg/common" - limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" -) - -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. - -// MetadataSource https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-enum-config-route-v3-ratelimit-action-metadata-source - -// +kubebuilder:validation:Enum=DYNAMIC;ROUTE_ENTRY -type MetadataSource string - -type GenericKeySpec struct { - DescriptorValue string `json:"descriptor_value"` - // +optional - DescriptorKey *string `json:"descriptor_key,omitempty"` -} - -type MetadataPathSegmentKey struct { - Key string `json:"key"` -} - -type MetadataPathSegment struct { - Segment MetadataPathSegmentKey `json:"segment"` -} - -type MetadataKeySpec struct { - Key string `json:"key"` - Path []MetadataPathSegment `json:"path"` -} - -type MetadataSpec struct { - DescriptorKey string `json:"descriptor_key"` - MetadataKey MetadataKeySpec `json:"metadata_key"` - // +optional - DefaultValue *string `json:"default_value,omitempty"` - // +kubebuilder:default=DYNAMIC - Source MetadataSource `json:"source,omitempty"` -} - -// RemoteAddressSpec no need to specify -// descriptor entry is populated using the trusted address from -// [x-forwarded-for](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#config-http-conn-man-headers-x-forwarded-for) -type RemoteAddressSpec struct { -} - -// RequestHeadersSpec Rate limit on request headers. -type RequestHeadersSpec struct { - HeaderName string `json:"header_name"` - DescriptorKey string `json:"descriptor_key"` - // +optional - SkipIfAbsent *bool `json:"skip_if_absent,omitempty"` -} - -// Action_Specifier defines one envoy rate limit action -type ActionSpecifier struct { - // +optional - GenericKey *GenericKeySpec `json:"generic_key,omitempty"` - // +optional - Metadata *MetadataSpec `json:"metadata,omitempty"` - // +optional - RemoteAddress *RemoteAddressSpec `json:"remote_address,omitempty"` - // +optional - RequestHeaders *RequestHeadersSpec `json:"request_headers,omitempty"` -} - -// Rule defines a single condition for the rate limit configuration -// All defined fields within the rule must be met to have a rule match -type Rule struct { - // +optional - Paths []string `json:"paths,omitempty"` - // +optional - Methods []string `json:"methods,omitempty"` - // +optional - Hosts []string `json:"hosts,omitempty"` -} - -// Configuration represents an action configuration. -// The equivalent of [config.route.v3.RateLimit](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-msg-config-route-v3-ratelimit) -// envoy object. -// Each action configuration produces, at most, one descriptor. -// Depending on the incoming request, one configuration may or may not produce -// a rate limit descriptor. -type Configuration struct { - // Actions holds list of action specifiers. Each action specifier can only define one action type. - Actions []ActionSpecifier `json:"actions"` -} - -// Limit represents partially a Limitador limit. -type Limit struct { - MaxValue int `json:"maxValue"` - Seconds int `json:"seconds"` - - // +optional - Conditions []string `json:"conditions,omitempty"` - // +optional - Variables []string `json:"variables,omitempty"` -} - -func LimitFromLimitadorRateLimit(limit *limitadorv1alpha1.RateLimit) *Limit { - if limit == nil { - return nil - } - - rlpLimit := &Limit{ - MaxValue: limit.MaxValue, - Seconds: limit.Seconds, - Conditions: nil, - Variables: nil, - } - - if limit.Conditions != nil { - // deep copy - rlpLimit.Conditions = make([]string, len(limit.Conditions)) - copy(rlpLimit.Conditions, limit.Conditions) - } - - if limit.Variables != nil { - // deep copy - rlpLimit.Variables = make([]string, len(limit.Variables)) - copy(rlpLimit.Variables, limit.Variables) - } - - return rlpLimit -} - -// RateLimit represents a complete rate limit configuration -type RateLimit struct { - // Configurations holds list of (action) configuration. - // +optional - Configurations []Configuration `json:"configurations,omitempty"` - - // Rules represents the definition of the scope of the rate limit object - // Defines a list of conditions for which rate limit configuration will apply - // Matching occurs when at least one rule applies against the incoming request. - // If rules are not set, or empty, it is equivalent to matching all the requests. - // +optional - Rules []Rule `json:"rules,omitempty"` - - // Limits holds a list of Limitador limits - // +optional - Limits []Limit `json:"limits,omitempty"` -} - -type GatewayRateLimits struct { - GatewayName string `json:"gatewayName"` - - // RateLimits holds the list of rate limit configurations - // +optional - RateLimits []RateLimit `json:"rateLimits,omitempty"` -} - -// RateLimitPolicySpec defines the desired state of RateLimitPolicy -type RateLimitPolicySpec struct { - // TargetRef identifies an API object to apply policy to. - TargetRef gatewayapiv1alpha2.PolicyTargetReference `json:"targetRef"` - // RateLimits holds the list of rate limit configurations - // +optional - RateLimits []RateLimit `json:"rateLimits,omitempty"` -} - -// RateLimitPolicyStatus defines the observed state of RateLimitPolicy -type RateLimitPolicyStatus struct { - // ObservedGeneration reflects the generation of the most recently observed spec. - // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty"` - - // Represents the observations of a foo's current state. - // Known .status.conditions.type are: "Available" - // +patchMergeKey=type - // +patchStrategy=merge - // +listType=map - // +listMapKey=type - Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` - - // GatewaysRateLimits shows the rate limit configuration applied by policies at the gateway level - // this field is only meant for rate limit policies targeting a route - // +optional - GatewaysRateLimits []GatewayRateLimits `json:"gatewaysRateLimits,omitempty"` -} - -func (s *RateLimitPolicyStatus) Equals(other *RateLimitPolicyStatus, logger logr.Logger) bool { - if s.ObservedGeneration != other.ObservedGeneration { - diff := cmp.Diff(s.ObservedGeneration, other.ObservedGeneration) - logger.V(1).Info("ObservedGeneration not equal", "difference", diff) - return false - } - - // Marshalling sorts by condition type - currentMarshaledJSON, _ := common.ConditionMarshal(s.Conditions) - otherMarshaledJSON, _ := common.ConditionMarshal(other.Conditions) - if string(currentMarshaledJSON) != string(otherMarshaledJSON) { - diff := cmp.Diff(string(currentMarshaledJSON), string(otherMarshaledJSON)) - logger.V(1).Info("Conditions not equal", "difference", diff) - return false - } - - // TODO(eastizle): reflect.DeepEqual does not work well with lists without order - if !reflect.DeepEqual(s.GatewaysRateLimits, other.GatewaysRateLimits) { - logger.V(1).Info("GatewaysRateLimits not equal") - return false - } - return true -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// RateLimitPolicy is the Schema for the ratelimitpolicies API -type RateLimitPolicy struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec RateLimitPolicySpec `json:"spec,omitempty"` - Status RateLimitPolicyStatus `json:"status,omitempty"` -} - -func (r *RateLimitPolicy) Validate() error { - if r.Spec.TargetRef.Group != gatewayapiv1alpha2.Group("gateway.networking.k8s.io") { - return fmt.Errorf("invalid targetRef.Group %s. The only supported group is gateway.networking.k8s.io", r.Spec.TargetRef.Group) - } - - if r.Spec.TargetRef.Kind != gatewayapiv1alpha2.Kind("HTTPRoute") && r.Spec.TargetRef.Kind != gatewayapiv1alpha2.Kind("Gateway") { - return fmt.Errorf("invalid targetRef.Kind %s. The only supported kind types are HTTPRoute and Gateway", r.Spec.TargetRef.Kind) - } - - if r.Spec.TargetRef.Namespace != nil && string(*r.Spec.TargetRef.Namespace) != r.Namespace { - return fmt.Errorf("invalid targetRef.Namespace %s. Currently only supporting references to the same namespace", *r.Spec.TargetRef.Namespace) - } - - return nil -} - -func (r *RateLimitPolicy) TargetKey() client.ObjectKey { - tmpNS := r.Namespace - if r.Spec.TargetRef.Namespace != nil { - tmpNS = string(*r.Spec.TargetRef.Namespace) - } - - return client.ObjectKey{ - Name: string(r.Spec.TargetRef.Name), - Namespace: tmpNS, - } -} - -// FlattenLimits returns Limit list from RateLimit list -func (r *RateLimitPolicy) FlattenLimits() []Limit { - flattenLimits := make([]Limit, 0) - - for idx := range r.Spec.RateLimits { - flattenLimits = append(flattenLimits, r.Spec.RateLimits[idx].Limits...) - } - - return flattenLimits -} - -//+kubebuilder:object:root=true - -// RateLimitPolicyList contains a list of RateLimitPolicy -type RateLimitPolicyList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []RateLimitPolicy `json:"items"` -} - -func (r *RateLimitPolicy) GetTargetRef() gatewayapiv1alpha2.PolicyTargetReference { - return r.Spec.TargetRef -} - -func (r *RateLimitPolicy) GetWrappedNamespace() gatewayapiv1alpha2.Namespace { - return gatewayapiv1alpha2.Namespace(r.Namespace) -} - -func (r *RateLimitPolicy) GetRulesHostnames() (ruleHosts []string) { - ruleHosts = make([]string, 0) - for idx := range r.Spec.RateLimits { - for ruleIdx := range r.Spec.RateLimits[idx].Rules { - ruleHosts = append(ruleHosts, r.Spec.RateLimits[idx].Rules[ruleIdx].Hosts...) - } - } - return -} - -func init() { - SchemeBuilder.Register(&RateLimitPolicy{}, &RateLimitPolicyList{}) -} diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 3fc493d56..c66c17f0f 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -27,41 +27,6 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ActionSpecifier) DeepCopyInto(out *ActionSpecifier) { - *out = *in - if in.GenericKey != nil { - in, out := &in.GenericKey, &out.GenericKey - *out = new(GenericKeySpec) - (*in).DeepCopyInto(*out) - } - if in.Metadata != nil { - in, out := &in.Metadata, &out.Metadata - *out = new(MetadataSpec) - (*in).DeepCopyInto(*out) - } - if in.RemoteAddress != nil { - in, out := &in.RemoteAddress, &out.RemoteAddress - *out = new(RemoteAddressSpec) - **out = **in - } - if in.RequestHeaders != nil { - in, out := &in.RequestHeaders, &out.RequestHeaders - *out = new(RequestHeadersSpec) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ActionSpecifier. -func (in *ActionSpecifier) DeepCopy() *ActionSpecifier { - if in == nil { - return nil - } - out := new(ActionSpecifier) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AuthPolicy) DeepCopyInto(out *AuthPolicy) { *out = *in @@ -281,70 +246,6 @@ func (in *AuthSchemeSpec) DeepCopy() *AuthSchemeSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Configuration) DeepCopyInto(out *Configuration) { - *out = *in - if in.Actions != nil { - in, out := &in.Actions, &out.Actions - *out = make([]ActionSpecifier, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Configuration. -func (in *Configuration) DeepCopy() *Configuration { - if in == nil { - return nil - } - out := new(Configuration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayRateLimits) DeepCopyInto(out *GatewayRateLimits) { - *out = *in - if in.RateLimits != nil { - in, out := &in.RateLimits, &out.RateLimits - *out = make([]RateLimit, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayRateLimits. -func (in *GatewayRateLimits) DeepCopy() *GatewayRateLimits { - if in == nil { - return nil - } - out := new(GatewayRateLimits) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GenericKeySpec) DeepCopyInto(out *GenericKeySpec) { - *out = *in - if in.DescriptorKey != nil { - in, out := &in.DescriptorKey, &out.DescriptorKey - *out = new(string) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericKeySpec. -func (in *GenericKeySpec) DeepCopy() *GenericKeySpec { - if in == nil { - return nil - } - out := new(GenericKeySpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Kuadrant) DeepCopyInto(out *Kuadrant) { *out = *in @@ -440,312 +341,3 @@ func (in *KuadrantStatus) DeepCopy() *KuadrantStatus { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Limit) DeepCopyInto(out *Limit) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Variables != nil { - in, out := &in.Variables, &out.Variables - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Limit. -func (in *Limit) DeepCopy() *Limit { - if in == nil { - return nil - } - out := new(Limit) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MetadataKeySpec) DeepCopyInto(out *MetadataKeySpec) { - *out = *in - if in.Path != nil { - in, out := &in.Path, &out.Path - *out = make([]MetadataPathSegment, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetadataKeySpec. -func (in *MetadataKeySpec) DeepCopy() *MetadataKeySpec { - if in == nil { - return nil - } - out := new(MetadataKeySpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MetadataPathSegment) DeepCopyInto(out *MetadataPathSegment) { - *out = *in - out.Segment = in.Segment -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetadataPathSegment. -func (in *MetadataPathSegment) DeepCopy() *MetadataPathSegment { - if in == nil { - return nil - } - out := new(MetadataPathSegment) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MetadataPathSegmentKey) DeepCopyInto(out *MetadataPathSegmentKey) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetadataPathSegmentKey. -func (in *MetadataPathSegmentKey) DeepCopy() *MetadataPathSegmentKey { - if in == nil { - return nil - } - out := new(MetadataPathSegmentKey) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MetadataSpec) DeepCopyInto(out *MetadataSpec) { - *out = *in - in.MetadataKey.DeepCopyInto(&out.MetadataKey) - if in.DefaultValue != nil { - in, out := &in.DefaultValue, &out.DefaultValue - *out = new(string) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetadataSpec. -func (in *MetadataSpec) DeepCopy() *MetadataSpec { - if in == nil { - return nil - } - out := new(MetadataSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RateLimit) DeepCopyInto(out *RateLimit) { - *out = *in - if in.Configurations != nil { - in, out := &in.Configurations, &out.Configurations - *out = make([]Configuration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Rules != nil { - in, out := &in.Rules, &out.Rules - *out = make([]Rule, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Limits != nil { - in, out := &in.Limits, &out.Limits - *out = make([]Limit, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimit. -func (in *RateLimit) DeepCopy() *RateLimit { - if in == nil { - return nil - } - out := new(RateLimit) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RateLimitPolicy) DeepCopyInto(out *RateLimitPolicy) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicy. -func (in *RateLimitPolicy) DeepCopy() *RateLimitPolicy { - if in == nil { - return nil - } - out := new(RateLimitPolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RateLimitPolicy) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RateLimitPolicyList) DeepCopyInto(out *RateLimitPolicyList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]RateLimitPolicy, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicyList. -func (in *RateLimitPolicyList) DeepCopy() *RateLimitPolicyList { - if in == nil { - return nil - } - out := new(RateLimitPolicyList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RateLimitPolicyList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RateLimitPolicySpec) DeepCopyInto(out *RateLimitPolicySpec) { - *out = *in - in.TargetRef.DeepCopyInto(&out.TargetRef) - if in.RateLimits != nil { - in, out := &in.RateLimits, &out.RateLimits - *out = make([]RateLimit, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicySpec. -func (in *RateLimitPolicySpec) DeepCopy() *RateLimitPolicySpec { - if in == nil { - return nil - } - out := new(RateLimitPolicySpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RateLimitPolicyStatus) DeepCopyInto(out *RateLimitPolicyStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.GatewaysRateLimits != nil { - in, out := &in.GatewaysRateLimits, &out.GatewaysRateLimits - *out = make([]GatewayRateLimits, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicyStatus. -func (in *RateLimitPolicyStatus) DeepCopy() *RateLimitPolicyStatus { - if in == nil { - return nil - } - out := new(RateLimitPolicyStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RemoteAddressSpec) DeepCopyInto(out *RemoteAddressSpec) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoteAddressSpec. -func (in *RemoteAddressSpec) DeepCopy() *RemoteAddressSpec { - if in == nil { - return nil - } - out := new(RemoteAddressSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RequestHeadersSpec) DeepCopyInto(out *RequestHeadersSpec) { - *out = *in - if in.SkipIfAbsent != nil { - in, out := &in.SkipIfAbsent, &out.SkipIfAbsent - *out = new(bool) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestHeadersSpec. -func (in *RequestHeadersSpec) DeepCopy() *RequestHeadersSpec { - if in == nil { - return nil - } - out := new(RequestHeadersSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Rule) DeepCopyInto(out *Rule) { - *out = *in - if in.Paths != nil { - in, out := &in.Paths, &out.Paths - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Methods != nil { - in, out := &in.Methods, &out.Methods - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Hosts != nil { - in, out := &in.Hosts, &out.Hosts - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule. -func (in *Rule) DeepCopy() *Rule { - if in == nil { - return nil - } - out := new(Rule) - in.DeepCopyInto(out) - return out -} diff --git a/api/v1beta2/groupversion_info.go b/api/v1beta2/groupversion_info.go new file mode 100644 index 000000000..ae18da7e8 --- /dev/null +++ b/api/v1beta2/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1beta1 contains API Schema definitions for the kuadrant v1beta1 API group +// +kubebuilder:object:generate=true +// +groupName=kuadrant.io +package v1beta2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "kuadrant.io", Version: "v1beta2"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/api/v1beta2/ratelimitpolicy_types.go b/api/v1beta2/ratelimitpolicy_types.go new file mode 100644 index 000000000..d056c62ea --- /dev/null +++ b/api/v1beta2/ratelimitpolicy_types.go @@ -0,0 +1,218 @@ +/* +Copyright 2021 Red Hat, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta2 + +import ( + "fmt" + "reflect" + + "github.com/go-logr/logr" + "github.com/google/go-cmp/cmp" + "github.com/kuadrant/kuadrant-operator/pkg/common" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// ContextSelector defines one item from the well known selectors +// TODO Document properly "Well-known selector" https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors +// +kubebuilder:validation:MinLength=1 +// +kubebuilder:validation:MaxLength=253 +type ContextSelector string + +// +kubebuilder:validation:Enum:=eq;neq;incl;excl;matches +type WhenConditionOperator string + +// +kubebuilder:validation:Enum:=second;minute;hour;day +type TimeUnit string + +// Rate defines the actual rate limit that will be used when there is a match +type Rate struct { + // Limit defines the max value allowed for a given period of time + Limit int `json:"limit"` + + // Duration defines the time period for which the Limit specified above applies. + Duration int `json:"duration"` + + // Duration defines the time uni + // Possible values are: "second", "minute", "hour", "day" + Unit TimeUnit `json:"unit"` +} + +// RouteSelector defines semantics for matching an HTTP request based on conditions +// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec +type WhenCondition struct { + // Selector defines one item from the well known selectors + // TODO Document properly "Well-known selector" https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors + Selector ContextSelector `json:"selector"` + + // The binary operator to be applied to the content fetched from the selector + // Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + Operator WhenConditionOperator `json:"operator"` + + // The value of reference for the comparison. + // If used with the "matches" operator, the value must compile to a valid Golang regex. + Value string `json:"value"` +} + +// RouteSelector defines semantics for matching an HTTP request based on conditions +// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec +type RouteSelector struct { + // Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request + // https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec + // +optional + Hostnames []gatewayapiv1alpha2.Hostname `json:"hostnames,omitempty"` + + // Matches define conditions used for matching the rule against incoming HTTP requests. + // https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec + // +optional + Matches []gatewayapiv1alpha2.HTTPRouteMatch `json:"matches,omitempty"` +} + +// Limit represents a complete rate limit configuration +type Limit struct { + // RouteSelectors defines semantics for matching an HTTP request based on conditions + // +optional + RouteSelectors []RouteSelector `json:"routeSelectors,omitempty"` + + // When holds the list of conditions for the policy to be enforced. + // Called also "soft" conditions as route selectors must also match + // +optional + When []WhenCondition `json:"when,omitempty"` + + // Counters defines additional rate limit counters based on context qualifiers and well known selectors + // TODO Document properly "Well-known selector" https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors + // +optional + Counters []ContextSelector `json:"counters,omitempty"` + + // Rates holds the list of limit rates + // +optional + Rates []Rate `json:"rates,omitempty"` +} + +// RateLimitPolicySpec defines the desired state of RateLimitPolicy +type RateLimitPolicySpec struct { + // TargetRef identifies an API object to apply policy to. + TargetRef gatewayapiv1alpha2.PolicyTargetReference `json:"targetRef"` + + // Limits holds the struct of limits indexed by a unique name + // +optional + Limits map[string]Limit `json:"limits,omitempty"` +} + +// RateLimitPolicyStatus defines the observed state of RateLimitPolicy +type RateLimitPolicyStatus struct { + // ObservedGeneration reflects the generation of the most recently observed spec. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // Represents the observations of a foo's current state. + // Known .status.conditions.type are: "Available" + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +} + +func (s *RateLimitPolicyStatus) Equals(other *RateLimitPolicyStatus, logger logr.Logger) bool { + if s.ObservedGeneration != other.ObservedGeneration { + diff := cmp.Diff(s.ObservedGeneration, other.ObservedGeneration) + logger.V(1).Info("ObservedGeneration not equal", "difference", diff) + return false + } + + // Marshalling sorts by condition type + currentMarshaledJSON, _ := common.ConditionMarshal(s.Conditions) + otherMarshaledJSON, _ := common.ConditionMarshal(other.Conditions) + if string(currentMarshaledJSON) != string(otherMarshaledJSON) { + diff := cmp.Diff(string(currentMarshaledJSON), string(otherMarshaledJSON)) + logger.V(1).Info("Conditions not equal", "difference", diff) + return false + } + + // TODO(eastizle): reflect.DeepEqual does not work well with lists without order + if !reflect.DeepEqual(s.GatewaysRateLimits, other.GatewaysRateLimits) { + logger.V(1).Info("GatewaysRateLimits not equal") + return false + } + return true +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// RateLimitPolicy is the Schema for the ratelimitpolicies API +type RateLimitPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RateLimitPolicySpec `json:"spec,omitempty"` + Status RateLimitPolicyStatus `json:"status,omitempty"` +} + +func (r *RateLimitPolicy) Validate() error { + if r.Spec.TargetRef.Group != gatewayapiv1alpha2.Group("gateway.networking.k8s.io") { + return fmt.Errorf("invalid targetRef.Group %s. The only supported group is gateway.networking.k8s.io", r.Spec.TargetRef.Group) + } + + if r.Spec.TargetRef.Kind != gatewayapiv1alpha2.Kind("HTTPRoute") && r.Spec.TargetRef.Kind != gatewayapiv1alpha2.Kind("Gateway") { + return fmt.Errorf("invalid targetRef.Kind %s. The only supported kind types are HTTPRoute and Gateway", r.Spec.TargetRef.Kind) + } + + if r.Spec.TargetRef.Namespace != nil && string(*r.Spec.TargetRef.Namespace) != r.Namespace { + return fmt.Errorf("invalid targetRef.Namespace %s. Currently only supporting references to the same namespace", *r.Spec.TargetRef.Namespace) + } + + return nil +} + +func (r *RateLimitPolicy) TargetKey() client.ObjectKey { + tmpNS := r.Namespace + if r.Spec.TargetRef.Namespace != nil { + tmpNS = string(*r.Spec.TargetRef.Namespace) + } + + return client.ObjectKey{ + Name: string(r.Spec.TargetRef.Name), + Namespace: tmpNS, + } +} + +//+kubebuilder:object:root=true + +// RateLimitPolicyList contains a list of RateLimitPolicy +type RateLimitPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RateLimitPolicy `json:"items"` +} + +func (r *RateLimitPolicy) GetTargetRef() gatewayapiv1alpha2.PolicyTargetReference { + return r.Spec.TargetRef +} + +func (r *RateLimitPolicy) GetWrappedNamespace() gatewayapiv1alpha2.Namespace { + return gatewayapiv1alpha2.Namespace(r.Namespace) +} + +func init() { + SchemeBuilder.Register(&RateLimitPolicy{}, &RateLimitPolicyList{}) +} diff --git a/api/v1beta1/ratelimitpolicy_types_test.go b/api/v1beta2/ratelimitpolicy_types_test.go similarity index 99% rename from api/v1beta1/ratelimitpolicy_types_test.go rename to api/v1beta2/ratelimitpolicy_types_test.go index d21bf1d1f..9664c5fb9 100644 --- a/api/v1beta1/ratelimitpolicy_types_test.go +++ b/api/v1beta2/ratelimitpolicy_types_test.go @@ -1,6 +1,6 @@ //go:build unit -package v1beta1 +package v1beta2 import ( "reflect" diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go new file mode 100644 index 000000000..1c7333895 --- /dev/null +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -0,0 +1,226 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta2 + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/gateway-api/apis/v1alpha2" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Limit) DeepCopyInto(out *Limit) { + *out = *in + if in.RouteSelectors != nil { + in, out := &in.RouteSelectors, &out.RouteSelectors + *out = make([]RouteSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.When != nil { + in, out := &in.When, &out.When + *out = make([]WhenCondition, len(*in)) + copy(*out, *in) + } + if in.Counters != nil { + in, out := &in.Counters, &out.Counters + *out = make([]ContextSelector, len(*in)) + copy(*out, *in) + } + if in.Rates != nil { + in, out := &in.Rates, &out.Rates + *out = make([]Rate, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Limit. +func (in *Limit) DeepCopy() *Limit { + if in == nil { + return nil + } + out := new(Limit) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Rate) DeepCopyInto(out *Rate) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rate. +func (in *Rate) DeepCopy() *Rate { + if in == nil { + return nil + } + out := new(Rate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RateLimitPolicy) DeepCopyInto(out *RateLimitPolicy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicy. +func (in *RateLimitPolicy) DeepCopy() *RateLimitPolicy { + if in == nil { + return nil + } + out := new(RateLimitPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RateLimitPolicy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RateLimitPolicyList) DeepCopyInto(out *RateLimitPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RateLimitPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicyList. +func (in *RateLimitPolicyList) DeepCopy() *RateLimitPolicyList { + if in == nil { + return nil + } + out := new(RateLimitPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RateLimitPolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RateLimitPolicySpec) DeepCopyInto(out *RateLimitPolicySpec) { + *out = *in + in.TargetRef.DeepCopyInto(&out.TargetRef) + if in.Limits != nil { + in, out := &in.Limits, &out.Limits + *out = make(map[string]Limit, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicySpec. +func (in *RateLimitPolicySpec) DeepCopy() *RateLimitPolicySpec { + if in == nil { + return nil + } + out := new(RateLimitPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RateLimitPolicyStatus) DeepCopyInto(out *RateLimitPolicyStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicyStatus. +func (in *RateLimitPolicyStatus) DeepCopy() *RateLimitPolicyStatus { + if in == nil { + return nil + } + out := new(RateLimitPolicyStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteSelector) DeepCopyInto(out *RouteSelector) { + *out = *in + if in.Hostnames != nil { + in, out := &in.Hostnames, &out.Hostnames + *out = make([]v1alpha2.Hostname, len(*in)) + copy(*out, *in) + } + if in.Matches != nil { + in, out := &in.Matches, &out.Matches + *out = make([]v1alpha2.HTTPRouteMatch, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteSelector. +func (in *RouteSelector) DeepCopy() *RouteSelector { + if in == nil { + return nil + } + out := new(RouteSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WhenCondition) DeepCopyInto(out *WhenCondition) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WhenCondition. +func (in *WhenCondition) DeepCopy() *WhenCondition { + if in == nil { + return nil + } + out := new(WhenCondition) + in.DeepCopyInto(out) + return out +} diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 61866a55b..c893739f8 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -25,33 +25,33 @@ spec: securityContext: runAsNonRoot: true containers: - - command: - - /manager - env: - - name: RELATED_IMAGE_WASMSHIM - value: "oci://quay.io/kuadrant/wasm-shim:latest" - image: controller:latest - name: manager - securityContext: - allowPrivilegeEscalation: false - livenessProbe: - httpGet: - path: /healthz - port: 8081 - initialDelaySeconds: 15 - periodSeconds: 20 - readinessProbe: - httpGet: - path: /readyz - port: 8081 - initialDelaySeconds: 5 - periodSeconds: 10 - resources: - limits: - cpu: 200m - memory: 300Mi - requests: - cpu: 200m - memory: 200Mi + - command: + - /manager + env: + - name: RELATED_IMAGE_WASMSHIM + value: "oci://quay.io/kuadrant/wasm-shim:latest" + image: controller:latest + name: manager + securityContext: + allowPrivilegeEscalation: false + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 200m + memory: 300Mi + requests: + cpu: 200m + memory: 200Mi serviceAccountName: controller-manager terminationGracePeriodSeconds: 10 From 23948f50ec6bb3f77c36fce5a19ce4d548e7b941 Mon Sep 17 00:00:00 2001 From: Eguzki Astiz Lezaun Date: Mon, 8 May 2023 11:30:30 +0200 Subject: [PATCH 2/4] RLP CRD v2: WhenConditionOperator only eq and neq --- api/v1beta2/ratelimitpolicy_types.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/v1beta2/ratelimitpolicy_types.go b/api/v1beta2/ratelimitpolicy_types.go index d056c62ea..59e896c60 100644 --- a/api/v1beta2/ratelimitpolicy_types.go +++ b/api/v1beta2/ratelimitpolicy_types.go @@ -37,7 +37,7 @@ import ( // +kubebuilder:validation:MaxLength=253 type ContextSelector string -// +kubebuilder:validation:Enum:=eq;neq;incl;excl;matches +// +kubebuilder:validation:Enum:=eq;neq type WhenConditionOperator string // +kubebuilder:validation:Enum:=second;minute;hour;day @@ -64,11 +64,10 @@ type WhenCondition struct { Selector ContextSelector `json:"selector"` // The binary operator to be applied to the content fetched from the selector - // Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + // Possible values are: "eq" (equal to), "neq" (not equal to) Operator WhenConditionOperator `json:"operator"` // The value of reference for the comparison. - // If used with the "matches" operator, the value must compile to a valid Golang regex. Value string `json:"value"` } From 3166bce17e6a6680547f4c8bbf0bf9685acdd37d Mon Sep 17 00:00:00 2001 From: Eguzki Astiz Lezaun Date: Thu, 18 May 2023 00:05:38 +0200 Subject: [PATCH 3/4] controller-gen paths for v1beta2 as well --- Makefile | 3 +- .../bases/kuadrant.io_ratelimitpolicies.yaml | 521 ++++++++---------- 2 files changed, 220 insertions(+), 304 deletions(-) diff --git a/Makefile b/Makefile index a1800824c..273bf32e1 100644 --- a/Makefile +++ b/Makefile @@ -222,7 +222,8 @@ act: $(ACT) ## Download act locally if necessary. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./api/v1beta1" output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) crd paths="./api/v1beta1;./api/v1beta2" output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) rbac:roleName=manager-role webhook paths="./..." .PHONY: dependencies-manifests dependencies-manifests: export AUTHORINO_OPERATOR_GITREF := $(AUTHORINO_OPERATOR_GITREF) diff --git a/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml b/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml index e907e9391..7528e551f 100644 --- a/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml +++ b/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml @@ -15,7 +15,7 @@ spec: singular: ratelimitpolicy scope: Namespaced versions: - - name: v1beta1 + - name: v1beta2 schema: openAPIV3Schema: description: RateLimitPolicy is the Schema for the ratelimitpolicies API @@ -35,176 +35,260 @@ spec: spec: description: RateLimitPolicySpec defines the desired state of RateLimitPolicy properties: - rateLimits: - description: RateLimits holds the list of rate limit configurations - items: - description: RateLimit represents a complete rate limit configuration + limits: + additionalProperties: + description: Limit represents a complete rate limit configuration properties: - configurations: - description: Configurations holds list of (action) configuration. + counters: + description: Counters defines additional rate limit counters + based on context qualifiers and well known selectors TODO + Document properly "Well-known selector" https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors items: - description: Configuration represents an action configuration. - The equivalent of [config.route.v3.RateLimit](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-msg-config-route-v3-ratelimit) - envoy object. Each action configuration produces, at most, - one descriptor. Depending on the incoming request, one configuration - may or may not produce a rate limit descriptor. - properties: - actions: - description: Actions holds list of action specifiers. - Each action specifier can only define one action type. - items: - description: Action_Specifier defines one envoy rate - limit action - properties: - generic_key: - properties: - descriptor_key: - type: string - descriptor_value: - type: string - required: - - descriptor_value - type: object - metadata: - properties: - default_value: - type: string - descriptor_key: - type: string - metadata_key: - properties: - key: - type: string - path: - items: - properties: - segment: - properties: - key: - type: string - required: - - key - type: object - required: - - segment - type: object - type: array - required: - - key - - path - type: object - source: - default: DYNAMIC - enum: - - DYNAMIC - - ROUTE_ENTRY - type: string - required: - - descriptor_key - - metadata_key - type: object - remote_address: - description: RemoteAddressSpec no need to specify - descriptor entry is populated using the trusted - address from [x-forwarded-for](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#config-http-conn-man-headers-x-forwarded-for) - type: object - request_headers: - description: RequestHeadersSpec Rate limit on request - headers. - properties: - descriptor_key: - type: string - header_name: - type: string - skip_if_absent: - type: boolean - required: - - descriptor_key - - header_name - type: object - type: object - type: array - required: - - actions - type: object + description: ContextSelector defines one item from the well + known selectors TODO Document properly "Well-known selector" + https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors + maxLength: 253 + minLength: 1 + type: string type: array - limits: - description: Limits holds a list of Limitador limits + rates: + description: Rates holds the list of limit rates items: - description: Limit represents partially a Limitador limit. + description: Rate defines the actual rate limit that will + be used when there is a match properties: - conditions: - items: - type: string - type: array - maxValue: + duration: + description: Duration defines the time period for which + the Limit specified above applies. type: integer - seconds: + limit: + description: Limit defines the max value allowed for a + given period of time type: integer - variables: - items: - type: string - type: array + unit: + description: 'Duration defines the time uni Possible values + are: "second", "minute", "hour", "day"' + enum: + - second + - minute + - hour + - day + type: string required: - - maxValue - - seconds + - duration + - limit + - unit type: object type: array - rules: - description: Rules represents the definition of the scope of - the rate limit object Defines a list of conditions for which - rate limit configuration will apply Matching occurs when at - least one rule applies against the incoming request. If rules - are not set, or empty, it is equivalent to matching all the - requests. + routeSelectors: + description: RouteSelectors defines semantics for matching an + HTTP request based on conditions items: - description: Rule defines a single condition for the rate - limit configuration All defined fields within the rule must - be met to have a rule match + description: RouteSelector defines semantics for matching + an HTTP request based on conditions https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec properties: - hosts: + hostnames: + description: Hostnames defines a set of hostname that + should match against the HTTP Host header to select + a HTTPRoute to process the request https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec items: + description: "Hostname is the fully qualified domain + name of a network host. This matches the RFC 1123 + definition of a hostname with 2 notable exceptions: + \n 1. IPs are not allowed. 2. A hostname may be prefixed + with a wildcard label (`*.`). The wildcard label must + appear by itself as the first label. \n Hostname can + be \"precise\" which is a domain name without the + terminating dot of a network host (e.g. \"foo.example.com\") + or \"wildcard\", which is a domain name prefixed with + a single wildcard label (e.g. `*.example.com`). \n + Note that as per RFC1035 and RFC1123, a *label* must + consist of lower case alphanumeric characters or '-', + and must start and end with an alphanumeric character. + No other punctuation is allowed." type: string type: array - methods: + matches: + description: Matches define conditions used for matching + the rule against incoming HTTP requests. https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec items: - type: string - type: array - paths: - items: - type: string + description: "HTTPRouteMatch defines the predicate used + to match requests to a given action. Multiple match + types are ANDed together, i.e. the match will evaluate + to true only if all conditions are satisfied. \n For + example, the match below will match a HTTP request + only if its path starts with `/foo` AND it contains + the `version: v1` header: \n ``` match: path: value: + \"/foo\" headers: - name: \"version\" value \"v1\" + ```" + properties: + headers: + description: Headers specifies HTTP request header + matchers. Multiple match values are ANDed together, + meaning, a request must match all the specified + headers to select the route. + items: + description: HTTPHeaderMatch describes how to + select a HTTP route by matching HTTP request + headers. + properties: + name: + description: "Name is the name of the HTTP + Header to be matched. Name matching MUST + be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + \n If multiple entries specify equivalent + header names, only the first entry with + an equivalent name MUST be considered for + a match. Subsequent entries with an equivalent + header name MUST be ignored. Due to the + case-insensitivity of header names, \"foo\" + and \"Foo\" are considered equivalent. \n + When a header is repeated in an HTTP request, + it is implementation-specific behavior as + to how this is represented. Generally, proxies + should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 + regarding processing a repeated header, + with special handling for \"Set-Cookie\"." + type: string + type: + description: "Type specifies how to match + against the value of the header. \n Support: + Core (Exact) \n Support: Custom (RegularExpression) + \n Since RegularExpression HeaderMatchType + has custom conformance, implementations + can support POSIX, PCRE or any other dialects + of regular expressions. Please read the + implementation's documentation to determine + the supported dialect." + type: string + value: + description: Value is the value of HTTP Header + to be matched. + type: string + required: + - name + - value + type: object + type: array + method: + description: "Method specifies HTTP method matcher. + When specified, this route will be matched only + if the request has the specified method. \n Support: + Extended" + type: string + path: + description: Path specifies a HTTP request path + matcher. If this field is not specified, a default + prefix match on the "/" path is provided. + properties: + type: + description: "Type specifies how to match against + the path Value. \n Support: Core (Exact, PathPrefix) + \n Support: Custom (RegularExpression)" + type: string + value: + description: Value of the HTTP path to match + against. + type: string + type: object + queryParams: + description: QueryParams specifies HTTP query parameter + matchers. Multiple match values are ANDed together, + meaning, a request must match all the specified + query parameters to select the route. + items: + description: HTTPQueryParamMatch describes how + to select a HTTP route by matching HTTP query + parameters. + properties: + name: + description: "Name is the name of the HTTP + query param to be matched. This must be + an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). + \n If multiple entries specify equivalent + query param names, only the first entry + with an equivalent name MUST be considered + for a match. Subsequent entries with an + equivalent query param name MUST be ignored." + type: string + type: + description: "Type specifies how to match + against the value of the query parameter. + \n Support: Extended (Exact) \n Support: + Custom (RegularExpression) \n Since RegularExpression + QueryParamMatchType has custom conformance, + implementations can support POSIX, PCRE + or any other dialects of regular expressions. + Please read the implementation's documentation + to determine the supported dialect." + type: string + value: + description: Value is the value of HTTP query + param to be matched. + type: string + required: + - name + - value + type: object + type: array + type: object type: array type: object type: array + when: + description: When holds the list of conditions for the policy + to be enforced. Called also "soft" conditions as route selectors + must also match + items: + description: RouteSelector defines semantics for matching + an HTTP request based on conditions https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec + properties: + operator: + description: 'The binary operator to be applied to the + content fetched from the selector Possible values are: + "eq" (equal to), "neq" (not equal to)' + enum: + - eq + - neq + type: string + selector: + description: Selector defines one item from the well known + selectors TODO Document properly "Well-known selector" + https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors + maxLength: 253 + minLength: 1 + type: string + value: + description: The value of reference for the comparison. + type: string + required: + - operator + - selector + - value + type: object + type: array type: object - type: array + description: Limits holds the struct of limits indexed by a unique + name + type: object targetRef: description: TargetRef identifies an API object to apply policy to. properties: group: description: Group is the group of the target resource. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string kind: description: Kind is kind of the target resource. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string name: description: Name is the name of the target resource. - maxLength: 253 - minLength: 1 type: string namespace: description: Namespace is the namespace of the referent. When unspecified, the local namespace is inferred. Even when policy targets a resource in a different namespace, it MUST only apply to traffic originating from the same namespace as the policy. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string required: - group @@ -241,7 +325,6 @@ spec: message: description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 type: string observedGeneration: description: observedGeneration represents the .metadata.generation @@ -250,7 +333,6 @@ spec: is 9, the condition is out of date with respect to the current state of the instance. format: int64 - minimum: 0 type: integer reason: description: reason contains a programmatic identifier indicating @@ -259,16 +341,9 @@ spec: meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ type: string status: description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown type: string type: description: type of condition in CamelCase or in foo.example.com/CamelCase. @@ -276,8 +351,6 @@ spec: like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string required: - lastTransitionTime @@ -290,164 +363,6 @@ spec: x-kubernetes-list-map-keys: - type x-kubernetes-list-type: map - gatewaysRateLimits: - description: GatewaysRateLimits shows the rate limit configuration - applied by policies at the gateway level this field is only meant - for rate limit policies targeting a route - items: - properties: - gatewayName: - type: string - rateLimits: - description: RateLimits holds the list of rate limit configurations - items: - description: RateLimit represents a complete rate limit configuration - properties: - configurations: - description: Configurations holds list of (action) configuration. - items: - description: Configuration represents an action configuration. - The equivalent of [config.route.v3.RateLimit](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-msg-config-route-v3-ratelimit) - envoy object. Each action configuration produces, - at most, one descriptor. Depending on the incoming - request, one configuration may or may not produce - a rate limit descriptor. - properties: - actions: - description: Actions holds list of action specifiers. - Each action specifier can only define one action - type. - items: - description: Action_Specifier defines one envoy - rate limit action - properties: - generic_key: - properties: - descriptor_key: - type: string - descriptor_value: - type: string - required: - - descriptor_value - type: object - metadata: - properties: - default_value: - type: string - descriptor_key: - type: string - metadata_key: - properties: - key: - type: string - path: - items: - properties: - segment: - properties: - key: - type: string - required: - - key - type: object - required: - - segment - type: object - type: array - required: - - key - - path - type: object - source: - default: DYNAMIC - enum: - - DYNAMIC - - ROUTE_ENTRY - type: string - required: - - descriptor_key - - metadata_key - type: object - remote_address: - description: RemoteAddressSpec no need to - specify descriptor entry is populated using - the trusted address from [x-forwarded-for](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#config-http-conn-man-headers-x-forwarded-for) - type: object - request_headers: - description: RequestHeadersSpec Rate limit - on request headers. - properties: - descriptor_key: - type: string - header_name: - type: string - skip_if_absent: - type: boolean - required: - - descriptor_key - - header_name - type: object - type: object - type: array - required: - - actions - type: object - type: array - limits: - description: Limits holds a list of Limitador limits - items: - description: Limit represents partially a Limitador - limit. - properties: - conditions: - items: - type: string - type: array - maxValue: - type: integer - seconds: - type: integer - variables: - items: - type: string - type: array - required: - - maxValue - - seconds - type: object - type: array - rules: - description: Rules represents the definition of the scope - of the rate limit object Defines a list of conditions - for which rate limit configuration will apply Matching - occurs when at least one rule applies against the incoming - request. If rules are not set, or empty, it is equivalent - to matching all the requests. - items: - description: Rule defines a single condition for the - rate limit configuration All defined fields within - the rule must be met to have a rule match - properties: - hosts: - items: - type: string - type: array - methods: - items: - type: string - type: array - paths: - items: - type: string - type: array - type: object - type: array - type: object - type: array - required: - - gatewayName - type: object - type: array observedGeneration: description: ObservedGeneration reflects the generation of the most recently observed spec. From 929a80268031faeac44380608496e6d2bacf4384 Mon Sep 17 00:00:00 2001 From: Eguzki Astiz Lezaun Date: Tue, 23 May 2023 14:00:53 +0200 Subject: [PATCH 4/4] minor enhancement about logging only in debug mode --- api/v1beta2/ratelimitpolicy_types.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/v1beta2/ratelimitpolicy_types.go b/api/v1beta2/ratelimitpolicy_types.go index 59e896c60..01e336e47 100644 --- a/api/v1beta2/ratelimitpolicy_types.go +++ b/api/v1beta2/ratelimitpolicy_types.go @@ -142,8 +142,10 @@ func (s *RateLimitPolicyStatus) Equals(other *RateLimitPolicyStatus, logger logr currentMarshaledJSON, _ := common.ConditionMarshal(s.Conditions) otherMarshaledJSON, _ := common.ConditionMarshal(other.Conditions) if string(currentMarshaledJSON) != string(otherMarshaledJSON) { - diff := cmp.Diff(string(currentMarshaledJSON), string(otherMarshaledJSON)) - logger.V(1).Info("Conditions not equal", "difference", diff) + if logger.V(1).Enabled() { + diff := cmp.Diff(string(currentMarshaledJSON), string(otherMarshaledJSON)) + logger.V(1).Info("Conditions not equal", "difference", diff) + } return false }