From 0adfe0cdcd101302958a911891c854ad3db0376b Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Mon, 21 Aug 2023 05:41:59 -0400 Subject: [PATCH] RLP v1beta2 (#230) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rpl v2 CRD * RLP CRD v2: WhenConditionOperator only eq and neq * controller-gen paths for v1beta2 as well * minor enhancement about logging only in debug mode * rlp v1beta2 controller * WASM plugin configuration object v2 * bundle update * fixup! bundle update * fix tests * WASMPlugin update to camel case * fix integration tests * wasm types moved to a common package * different selectors share type: ContextSelector --------- Co-authored-by: Eguzki Astiz Lezaun * [rlp-v2] WasmPlugin reconciliation (#204) * reconciliation of route selectors for RLPs targeting HTTPRoutes * Change `common.Contains(slice []string, target string)` to any comparable type (using generics) * Define a `common.HTTPRouteRuleSelector` type that checks whether a `HTTPRouteMatch` selects a `HTTPRouteRule` or not * Define a `rlptools.HTTPRouteRulesFromRouteSelector` function that returns all rules from a HTTPRoute that a route selector selects (using `common.HTTPRouteRuleSelector`) * Modify `rlptools.conditionsFromLimit` to use `rlptools.HTTPRouteRulesFromRouteSelector` and generate wasm conditions for each combination of selected route rule match and hostname * Ensure all generated route conditions include a pattern for the hostnames (route selectors’, HTTPRoute’s or Gateway’s) * Ensure all generated route conditions include the user-defined `when` conditions * add tests for generating a wasmplugin config out of a complex httproute and complex ratelimitpolicy The httproute declares 2 rules and 2 hostnames. The RLP declares 2 limits: - one limit declares one counter, targets one of the httprouterules, for one hostname only, with an additional 'when' condition and counter qualifier; - the other limit declares 2 counters, targets the other httprouterule, for all hostnames, and states no additional 'when' condition nor counter qualifier. * generate wasm rules only for routeSelector-filtered hostnames that belong to the targeted route * fix: pattern expression for hostnames with the right operator - introduces 'endswith' pattern operator * fix: compute wasm rules only for the applicable hostnames Passes a list of hostnames into the call to the function that generates the wasm rules for a given RLP, notoriously to compute the conditions when to apply each limit. The list of hostnames is the domain intersection between the hostnames specified in the HTTPRoute and its parent gateway for which the wasm config is being built. The route selection considers this as a boundary, so it doesn’t generate rules for the wrong gateway/hostname. Fixes the computation of conditions when no HTTPRouteRule matches the route selectors. * do not generate wasm condition patterns for the hostname when all apply * do not repeat RLPs for each hostname within the wasm config * `rlptools.HTTPRouteRulesFromRouteSelector` moved to `api/v1beta2/route_selectors.go` along with the `RouteSelector` type itself + no longer injecting a separate list of hostnames for avoiding building wasm rules for hostnames that do not apply, as this did not seem to belong to the route selector logic anyway. * reconciliation of route selectors for RLPs targeting Gateways Get all HTTPRoutes that are children of the Gateway and call the generation of the set of wasm rules (conditions and data for each limit) for each HTTPRoute * match the gateway hostnames instead of the httproute ones when building the wasm rules for rlps that target a gateway * refactor: make HTTPRouteRuleSelector.Selects return sooner if the HTTP method does not match * refactor: ensure wasm rule conditions are generated out of selected HTTPRouteRules in the same order as the selectors select the rules * Prevent the usage of routeSelectors in RLPs that target a Gateway * do not generate conditions for gateway policy wasm rules from httproutes that have a rlp of its own and avoid building wasmplugins when there are no rules to apply * avoid adding rlps without any rules to the wasm config * fix: do not use gateway name nor host in the RL domain Fixes the reconciliation of the Limitador CR, pairing it with the reconciled wasm config (WasmPlugin CR), so - rate limit definitions won't be duplicated in the Limitador CR; - limits can be defined crossing gateways and hostnames and yet be treated as the same limit in Limitador (case of simple RLPs that target HTTPRoutes with multiple Gateway parent refs) * Limit names in the Limitador conditions and RL request data generated in the format: limit.__, where is sanitised to include only characters allowed by Limitador for the identifiers and is generated out of the original limit name to avoid breaking uniqueness of the name after sanitisation. * fix: missing rebase changes & update istio deps For some reason after rebase, there is some dependency issue. This was fixed by updating the istio dep to https://github.com/istio/istio/releases/tag/1.17.5 * fix: lint issues * refactor: lint change for CountersAsStringList() and deleteLimits() Co-authored-by: Guilherme Cassolato --------- Co-authored-by: Eguzki Astiz Lezaun Co-authored-by: Guilherme Cassolato Co-authored-by: KevFan --- Makefile | 4 +- api/v1beta1/ratelimitpolicy_types.go | 314 ----- api/v1beta1/zz_generated.deepcopy.go | 408 ------ api/v1beta2/groupversion_info.go | 36 + api/v1beta2/ratelimitpolicy_types.go | 246 ++++ .../ratelimitpolicy_types_test.go | 86 +- api/v1beta2/route_selectors.go | 53 + api/v1beta2/route_selectors_test.go | 211 +++ api/v1beta2/zz_generated.deepcopy.go | 226 +++ ...adrant-operator.clusterserviceversion.yaml | 52 +- .../kuadrant.io_ratelimitpolicies.yaml | 552 ++++---- .../bases/kuadrant.io_ratelimitpolicies.yaml | 543 ++++---- config/crd/kustomization.yaml | 6 - ...openapi_validation_in_ratelimitpolicy.yaml | 8 - .../kuadrant_v1beta1_ratelimitpolicy.yaml | 29 - .../kuadrant_v1beta2_ratelimitpolicy.yaml | 16 + config/samples/kustomization.yaml | 2 +- controllers/gateway_rlp_eventmapper.go | 4 +- .../ratelimitpolicy_cluster_envoy_filter.go | 4 +- controllers/ratelimitpolicy_controller.go | 22 +- .../ratelimitpolicy_controller_test.go | 717 ++++++---- controllers/ratelimitpolicy_limits.go | 196 +-- controllers/ratelimitpolicy_status.go | 73 +- controllers/ratelimitpolicy_wasm_plugins.go | 144 +- controllers/suite_test.go | 4 + go.mod | 111 +- go.sum | 1222 ++--------------- main.go | 2 + pkg/common/common.go | 81 +- pkg/common/common_test.go | 361 ++++- pkg/common/gatewayapi_utils.go | 136 +- pkg/common/gatewayapi_utils_test.go | 437 ++++++ pkg/reconcilers/targetref_reconciler.go | 69 + pkg/rlptools/limit_index.go | 271 ---- pkg/rlptools/limit_index_test.go | 719 ---------- pkg/rlptools/rate_limit_index.go | 130 ++ pkg/rlptools/rate_limit_index_test.go | 140 ++ pkg/rlptools/utils.go | 90 ++ pkg/rlptools/utils_test.go | 365 +++++ pkg/rlptools/wasm/types.go | 116 ++ pkg/rlptools/wasm_utils.go | 290 ++-- pkg/rlptools/wasm_utils_test.go | 448 +++--- 42 files changed, 4323 insertions(+), 4621 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 (59%) create mode 100644 api/v1beta2/route_selectors.go create mode 100644 api/v1beta2/route_selectors_test.go create mode 100644 api/v1beta2/zz_generated.deepcopy.go delete mode 100644 config/crd/patches/action_specifier_oneof_openapi_validation_in_ratelimitpolicy.yaml delete mode 100644 config/samples/kuadrant_v1beta1_ratelimitpolicy.yaml create mode 100644 config/samples/kuadrant_v1beta2_ratelimitpolicy.yaml delete mode 100644 pkg/rlptools/limit_index.go delete mode 100644 pkg/rlptools/limit_index_test.go create mode 100644 pkg/rlptools/rate_limit_index.go create mode 100644 pkg/rlptools/rate_limit_index_test.go create mode 100644 pkg/rlptools/utils.go create mode 100644 pkg/rlptools/utils_test.go create mode 100644 pkg/rlptools/wasm/types.go diff --git a/Makefile b/Makefile index 124c398b9..790b10255 100644 --- a/Makefile +++ b/Makefile @@ -230,8 +230,8 @@ act: $(ACT) ## Download act locally if necessary. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) crd webhook paths="./api/v1beta1" output:crd:artifacts:config=config/crd/bases - $(CONTROLLER_GEN) rbac:roleName=manager-role paths="./..." + $(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/api/v1beta1/ratelimitpolicy_types.go b/api/v1beta1/ratelimitpolicy_types.go deleted file mode 100644 index 6b7b36c17..000000000 --- a/api/v1beta1/ratelimitpolicy_types.go +++ /dev/null @@ -1,314 +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" - gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// 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 != ("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 != ("HTTPRoute") && r.Spec.TargetRef.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() gatewayapiv1beta1.Namespace { - return gatewayapiv1beta1.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..753236206 --- /dev/null +++ b/api/v1beta2/ratelimitpolicy_types.go @@ -0,0 +1,246 @@ +/* +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" + + "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" + gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// 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 attributes +// Attributes: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes +// Well-known selectors: https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors +// They are named by a dot-separated path (e.g. request.path) +// Example: "request.path" -> The path portion of the URL +// +kubebuilder:validation:MinLength=1 +// +kubebuilder:validation:MaxLength=253 +type ContextSelector string + +// +kubebuilder:validation:Enum:=eq;neq;startswith;endswith;incl;excl;matches +type WhenConditionOperator string + +const ( + EqualOperator WhenConditionOperator = "eq" + NotEqualOperator WhenConditionOperator = "neq" + StartsWithOperator WhenConditionOperator = "startswith" + EndsWithOperator WhenConditionOperator = "endswith" + IncludeOperator WhenConditionOperator = "incl" + ExcludeOperator WhenConditionOperator = "excl" + MatchesOperator WhenConditionOperator = "matches" +) + +// +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) + Operator WhenConditionOperator `json:"operator"` + + // The value of reference for the comparison. + Value string `json:"value"` +} + +// 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"` +} + +func (l Limit) CountersAsStringList() []string { + if len(l.Counters) == 0 { + return nil + } + return common.Map(l.Counters, func(counter ContextSelector) string { return string(counter) }) +} + +// 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) { + if logger.V(1).Enabled() { + diff := cmp.Diff(string(currentMarshaledJSON), string(otherMarshaledJSON)) + logger.V(1).Info("Conditions not equal", "difference", diff) + } + 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 != ("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 != ("HTTPRoute") && r.Spec.TargetRef.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) + } + + // prevents usage of routeSelectors in a gateway RLP + if r.Spec.TargetRef.Kind == ("Gateway") { + for _, limit := range r.Spec.Limits { + if len(limit.RouteSelectors) > 0 { + return fmt.Errorf("route selectors not supported when targeting a Gateway") + } + } + } + + 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() gatewayapiv1beta1.Namespace { + return gatewayapiv1beta1.Namespace(r.Namespace) +} + +func (r *RateLimitPolicy) GetRulesHostnames() (ruleHosts []string) { + ruleHosts = make([]string, 0) + for _, limit := range r.Spec.Limits { + for _, routeSelector := range limit.RouteSelectors { + convertHostnamesToString := func(gwHostnames []gatewayapiv1beta1.Hostname) []string { + hostnames := make([]string, 0, len(gwHostnames)) + for _, gwHostName := range gwHostnames { + hostnames = append(hostnames, string(gwHostName)) + } + return hostnames + } + ruleHosts = append(ruleHosts, convertHostnamesToString(routeSelector.Hostnames)...) + } + } + return +} + +func init() { + SchemeBuilder.Register(&RateLimitPolicy{}, &RateLimitPolicyList{}) +} diff --git a/api/v1beta1/ratelimitpolicy_types_test.go b/api/v1beta2/ratelimitpolicy_types_test.go similarity index 59% rename from api/v1beta1/ratelimitpolicy_types_test.go rename to api/v1beta2/ratelimitpolicy_types_test.go index 16af8d115..351789e3e 100644 --- a/api/v1beta1/ratelimitpolicy_types_test.go +++ b/api/v1beta2/ratelimitpolicy_types_test.go @@ -1,13 +1,11 @@ //go:build unit -package v1beta1 +package v1beta2 import ( - "reflect" "strings" "testing" - limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -94,85 +92,3 @@ func TestRateLimitPolicyValidation(t *testing.T) { t.Fatalf(`rlp.Validate() did not return expected error. Instead: %v`, err) } } - -func TestLimitFromLimitadorRateLimit(t *testing.T) { - testCases := []struct { - name string - limit *limitadorv1alpha1.RateLimit - expected *Limit - }{ - { - "nil conditions", - &limitadorv1alpha1.RateLimit{ - Namespace: "someNS", - MaxValue: 1, - Seconds: 2, - Conditions: nil, - Variables: []string{"a", "b"}, - }, - &Limit{ - MaxValue: 1, - Seconds: 2, - Conditions: nil, - Variables: []string{"a", "b"}, - }, - }, - { - "empty conditions", - &limitadorv1alpha1.RateLimit{ - Namespace: "someNS", - MaxValue: 1, - Seconds: 2, - Conditions: make([]string, 0), - Variables: []string{"a", "b"}, - }, - &Limit{ - MaxValue: 1, - Seconds: 2, - Conditions: make([]string, 0), - Variables: []string{"a", "b"}, - }, - }, - { - "nil variables", - &limitadorv1alpha1.RateLimit{ - Namespace: "someNS", - MaxValue: 1, - Seconds: 2, - Conditions: []string{"a", "b"}, - Variables: nil, - }, - &Limit{ - MaxValue: 1, - Seconds: 2, - Conditions: []string{"a", "b"}, - Variables: nil, - }, - }, - { - "empty variables", - &limitadorv1alpha1.RateLimit{ - Namespace: "someNS", - MaxValue: 1, - Seconds: 2, - Conditions: []string{"a", "b"}, - Variables: make([]string, 0), - }, - &Limit{ - MaxValue: 1, - Seconds: 2, - Conditions: []string{"a", "b"}, - Variables: make([]string, 0), - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(subT *testing.T) { - rlpLimit := LimitFromLimitadorRateLimit(tc.limit) - if !reflect.DeepEqual(rlpLimit, tc.expected) { - subT.Error("expected object does not match") - } - }) - } -} diff --git a/api/v1beta2/route_selectors.go b/api/v1beta2/route_selectors.go new file mode 100644 index 000000000..cec7936d8 --- /dev/null +++ b/api/v1beta2/route_selectors.go @@ -0,0 +1,53 @@ +package v1beta2 + +import ( + gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + orderedmap "github.com/elliotchance/orderedmap/v2" + + "github.com/kuadrant/kuadrant-operator/pkg/common" +) + +// 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 []gatewayapiv1beta1.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 []gatewayapiv1beta1.HTTPRouteMatch `json:"matches,omitempty"` +} + +// SelectRules returns, from a HTTPRoute, all HTTPRouteRules that either specify no HTTRouteMatches or that contain at +// least one HTTRouteMatch whose statements expressly include (partially or totally) the statements of at least one of +// the matches of the selector. If the selector does not specify any matches, then all HTTPRouteRules are selected. +// +// Additionally, if the selector specifies a non-empty list of hostnames, a non-empty intersection between the literal +// hostnames of the selector and set of hostnames specified in the HTTPRoute must exist. Otherwise, the function +// returns nil. +func (s *RouteSelector) SelectRules(route *gatewayapiv1beta1.HTTPRoute) (rules []gatewayapiv1beta1.HTTPRouteRule) { + rulesIndices := orderedmap.NewOrderedMap[int, gatewayapiv1beta1.HTTPRouteRule]() + if len(s.Hostnames) > 0 && !common.Intersect(s.Hostnames, route.Spec.Hostnames) { + return nil + } + if len(s.Matches) == 0 { + return route.Spec.Rules + } + for idx := range s.Matches { + routeSelectorMatch := s.Matches[idx] + for idx, rule := range route.Spec.Rules { + rs := common.HTTPRouteRuleSelector{HTTPRouteMatch: &routeSelectorMatch} + if rs.Selects(rule) { + rulesIndices.Set(idx, rule) + } + } + } + for el := rulesIndices.Front(); el != nil; el = el.Next() { + rules = append(rules, el.Value) + } + return +} diff --git a/api/v1beta2/route_selectors_test.go b/api/v1beta2/route_selectors_test.go new file mode 100644 index 000000000..347dbe20e --- /dev/null +++ b/api/v1beta2/route_selectors_test.go @@ -0,0 +1,211 @@ +//go:build unit + +package v1beta2 + +import ( + "fmt" + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/kuadrant/kuadrant-operator/pkg/common" +) + +func TestRouteSelectors(t *testing.T) { + gatewayHostnames := []gatewayapiv1beta1.Hostname{ + "*.toystore.com", + } + + gateway := &gatewayapiv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-gateway", + }, + } + + for _, hostname := range gatewayHostnames { + gateway.Spec.Listeners = append(gateway.Spec.Listeners, gatewayapiv1beta1.Listener{Hostname: &hostname}) + } + + route := &gatewayapiv1beta1.HTTPRoute{ + Spec: gatewayapiv1beta1.HTTPRouteSpec{ + CommonRouteSpec: gatewayapiv1beta1.CommonRouteSpec{ + ParentRefs: []gatewayapiv1beta1.ParentReference{ + { + Name: gatewayapiv1beta1.ObjectName(gateway.Name), + }, + }, + }, + Hostnames: []gatewayapiv1beta1.Hostname{"api.toystore.com"}, + Rules: []gatewayapiv1beta1.HTTPRouteRule{ + { + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + // get /toys* + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchPathPrefix}[0], + Value: &[]string{"/toy"}[0], + }, + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethod("GET")}[0], + }, + // post /toys* + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchPathPrefix}[0], + Value: &[]string{"/toy"}[0], + }, + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethod("POST")}[0], + }, + }, + }, + { + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + // /assets* + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchPathPrefix}[0], + Value: &[]string{"/assets"}[0], + }, + }, + }, + }, + }, + }, + } + + testCases := []struct { + name string + routeSelector RouteSelector + route *gatewayapiv1beta1.HTTPRoute + expected []gatewayapiv1beta1.HTTPRouteRule + }{ + { + name: "empty route selector selects all HTTPRouteRules", + routeSelector: RouteSelector{}, + route: route, + expected: route.Spec.Rules, + }, + { + name: "route selector selects the HTTPRouteRules whose set of HTTPRouteMatch is a perfect match", + routeSelector: RouteSelector{ + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchPathPrefix}[0], + Value: &[]string{"/assets"}[0], + }, + }, + }, + }, + route: route, + expected: []gatewayapiv1beta1.HTTPRouteRule{route.Spec.Rules[1]}, + }, + { + name: "route selector selects the HTTPRouteRules whose set of HTTPRouteMatch contains at least one match", + routeSelector: RouteSelector{ + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchPathPrefix}[0], + Value: &[]string{"/toy"}[0], + }, + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethod("POST")}[0], + }, + }, + }, + route: route, + expected: []gatewayapiv1beta1.HTTPRouteRule{route.Spec.Rules[0]}, + }, + { + name: "route selector with missing part of a HTTPRouteMatch still selects the HTTPRouteRules that match", + routeSelector: RouteSelector{ + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchPathPrefix}[0], + Value: &[]string{"/toy"}[0], + }, + }, + }, + }, + route: route, + expected: []gatewayapiv1beta1.HTTPRouteRule{route.Spec.Rules[0]}, + }, + { + name: "route selector selects no HTTPRouteRule when no criterion matches", + routeSelector: RouteSelector{ + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchExact}[0], + Value: &[]string{"/toy"}[0], + }, + }, + }, + }, + route: route, + expected: nil, + }, + { + name: "route selector selects the HTTPRouteRules whose HTTPRoute's hostnames match the selector", + routeSelector: RouteSelector{ + Hostnames: []gatewayapiv1beta1.Hostname{"api.toystore.com"}, + }, + route: route, + expected: route.Spec.Rules, + }, + { + name: "route selector selects the HTTPRouteRules whose HTTPRoute's hostnames match the selector additionally to other criteria", + routeSelector: RouteSelector{ + Hostnames: []gatewayapiv1beta1.Hostname{"api.toystore.com"}, + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchPathPrefix}[0], + Value: &[]string{"/toy"}[0], + }, + }, + }, + }, + route: route, + expected: []gatewayapiv1beta1.HTTPRouteRule{route.Spec.Rules[0]}, + }, + { + name: "route selector does not select HTTPRouteRules whose HTTPRoute's hostnames do not match the selector", + routeSelector: RouteSelector{ + Hostnames: []gatewayapiv1beta1.Hostname{"www.toystore.com"}, + }, + route: route, + expected: nil, + }, + { + name: "route selector does not select HTTPRouteRules whose HTTPRoute's hostnames do not match the selector even when other criteria match", + routeSelector: RouteSelector{ + Hostnames: []gatewayapiv1beta1.Hostname{"www.toystore.com"}, + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchPathPrefix}[0], + Value: &[]string{"/toy"}[0], + }, + }, + }, + }, + route: route, + expected: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rules := tc.routeSelector.SelectRules(tc.route) + rulesToStringSlice := func(rules []gatewayapiv1beta1.HTTPRouteRule) []string { + return common.Map(common.Map(rules, common.HTTPRouteRuleToString), func(r string) string { return fmt.Sprintf("{%s}", r) }) + } + if !reflect.DeepEqual(rules, tc.expected) { + t.Errorf("expected %v, got %v", rulesToStringSlice(tc.expected), rulesToStringSlice(rules)) + } + }) + } +} diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go new file mode 100644 index 000000000..94ad44d76 --- /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/v1beta1" +) + +// 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([]v1beta1.Hostname, len(*in)) + copy(*out, *in) + } + if in.Matches != nil { + in, out := &in.Matches, &out.Matches + *out = make([]v1beta1.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/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml b/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml index 339fcb9ef..cf15c79cf 100644 --- a/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml +++ b/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml @@ -64,53 +64,23 @@ metadata: "spec": {} }, { - "apiVersion": "kuadrant.io/v1beta1", + "apiVersion": "kuadrant.io/v1beta2", "kind": "RateLimitPolicy", "metadata": { "name": "toystore" }, "spec": { - "rateLimits": [ - { - "configurations": [ - { - "actions": [ - { - "metadata": { - "default_value": "no-user", - "descriptor_key": "userID", - "metadata_key": { - "key": "envoy.filters.http.ext_authz", - "path": [ - { - "segment": { - "key": "ext_auth_data" - } - }, - { - "segment": { - "key": "userID" - } - } - ] - } - } - } - ] - } - ], - "limits": [ + "limits": { + "toys": { + "rates": [ { - "conditions": [], - "maxValue": 2, - "seconds": 10, - "variables": [ - "userID" - ] + "duration": 1, + "limit": 50, + "unit": "minute" } ] } - ], + }, "targetRef": { "group": "gateway.networking.k8s.io", "kind": "HTTPRoute", @@ -143,11 +113,9 @@ spec: kind: Kuadrant name: kuadrants.kuadrant.io version: v1beta1 - - description: Enable access control on workloads based on HTTP rate limiting - displayName: RateLimitPolicy - kind: RateLimitPolicy + - kind: RateLimitPolicy name: ratelimitpolicies.kuadrant.io - version: v1beta1 + version: v1beta2 description: A Kubernetes Operator to manage the lifecycle of the Kuadrant system displayName: Kuadrant Operator icon: diff --git a/bundle/manifests/kuadrant.io_ratelimitpolicies.yaml b/bundle/manifests/kuadrant.io_ratelimitpolicies.yaml index 870bab244..af8a4c3dc 100644 --- a/bundle/manifests/kuadrant.io_ratelimitpolicies.yaml +++ b/bundle/manifests/kuadrant.io_ratelimitpolicies.yaml @@ -16,7 +16,7 @@ spec: singular: ratelimitpolicy scope: Namespaced versions: - - name: v1beta1 + - name: v1beta2 schema: openAPIV3Schema: description: RateLimitPolicy is the Schema for the ratelimitpolicies API @@ -36,185 +36,282 @@ 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 - oneOf: - - required: - - generic_key - - required: - - metadata - - required: - - remote_address - - required: - - request_headers - 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 attributes Attributes: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes + Well-known selectors: https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors + They are named by a dot-separated path (e.g. request.path) + Example: "request.path" -> The path portion of the URL' + 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: \n path: value: + \"/foo\" headers: - name: \"version\" value \"v1\" + \n ```" + 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: Implementation-specific + (RegularExpression) \n Since RegularExpression + HeaderMatchType has implementation-specific + 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: Implementation-specific (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. \n Support: + Extended" + 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. + \n If a query param is repeated in an HTTP + request, the behavior is purposely left + undefined, since different data planes have + different capabilities. However, it is *recommended* + that implementations should match against + the first value of the param if the data + plane supports it, as this behavior is expected + in other load balancing contexts outside + of the Gateway API. \n Users SHOULD NOT + route traffic based on repeated query params + to guard themselves against potential differences + in the implementations." + type: string + type: + description: "Type specifies how to match + against the value of the query parameter. + \n Support: Extended (Exact) \n Support: + Implementation-specific (RegularExpression) + \n Since RegularExpression QueryParamMatchType + has Implementation-specific 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 + - startswith + - endswith + - incl + - excl + - matches + 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 @@ -251,7 +348,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 @@ -260,7 +356,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 @@ -269,16 +364,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. @@ -286,8 +374,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 @@ -300,164 +386,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. diff --git a/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml b/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml index cda61dd41..1c398ed41 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,282 @@ 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 attributes Attributes: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes + Well-known selectors: https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors + They are named by a dot-separated path (e.g. request.path) + Example: "request.path" -> The path portion of the URL' + 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: \n path: value: + \"/foo\" headers: - name: \"version\" value \"v1\" + \n ```" + 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: Implementation-specific + (RegularExpression) \n Since RegularExpression + HeaderMatchType has implementation-specific + 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: Implementation-specific (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. \n Support: + Extended" + 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. + \n If a query param is repeated in an HTTP + request, the behavior is purposely left + undefined, since different data planes have + different capabilities. However, it is *recommended* + that implementations should match against + the first value of the param if the data + plane supports it, as this behavior is expected + in other load balancing contexts outside + of the Gateway API. \n Users SHOULD NOT + route traffic based on repeated query params + to guard themselves against potential differences + in the implementations." + type: string + type: + description: "Type specifies how to match + against the value of the query parameter. + \n Support: Extended (Exact) \n Support: + Implementation-specific (RegularExpression) + \n Since RegularExpression QueryParamMatchType + has Implementation-specific 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 + - startswith + - endswith + - incl + - excl + - matches + 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 +347,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 +355,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 +363,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 +373,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 +385,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. diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index f463150ba..4f05910c6 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -19,12 +19,6 @@ patchesStrategicMerge: #+kubebuilder:scaffold:crdkustomizecainjectionpatch patchesJson6902: - - path: patches/action_specifier_oneof_openapi_validation_in_ratelimitpolicy.yaml - target: - group: apiextensions.k8s.io - version: v1 - kind: CustomResourceDefinition - name: ratelimitpolicies.kuadrant.io # the following config is for teaching kustomize how to do kustomization for CRDs. configurations: diff --git a/config/crd/patches/action_specifier_oneof_openapi_validation_in_ratelimitpolicy.yaml b/config/crd/patches/action_specifier_oneof_openapi_validation_in_ratelimitpolicy.yaml deleted file mode 100644 index 4c0fb878f..000000000 --- a/config/crd/patches/action_specifier_oneof_openapi_validation_in_ratelimitpolicy.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -- op: add - path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/rateLimits/items/properties/configurations/items/properties/actions/items/oneOf - value: - - required: ["generic_key"] - - required: ["metadata"] - - required: ["remote_address"] - - required: ["request_headers"] diff --git a/config/samples/kuadrant_v1beta1_ratelimitpolicy.yaml b/config/samples/kuadrant_v1beta1_ratelimitpolicy.yaml deleted file mode 100644 index 34890c030..000000000 --- a/config/samples/kuadrant_v1beta1_ratelimitpolicy.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- -apiVersion: kuadrant.io/v1beta1 -kind: RateLimitPolicy -metadata: - name: toystore -spec: - targetRef: - group: gateway.networking.k8s.io - kind: HTTPRoute - name: toystore - rateLimits: - - configurations: - - actions: - - metadata: - descriptor_key: "userID" - default_value: "no-user" - metadata_key: - key: "envoy.filters.http.ext_authz" - path: - - segment: - key: "ext_auth_data" - - segment: - key: "userID" - limits: - - conditions: [] - maxValue: 2 - seconds: 10 - variables: - - userID diff --git a/config/samples/kuadrant_v1beta2_ratelimitpolicy.yaml b/config/samples/kuadrant_v1beta2_ratelimitpolicy.yaml new file mode 100644 index 000000000..6fa97658a --- /dev/null +++ b/config/samples/kuadrant_v1beta2_ratelimitpolicy.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: kuadrant.io/v1beta2 +kind: RateLimitPolicy +metadata: + name: toystore +spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: toystore + limits: + toys: + rates: + - limit: 50 + duration: 1 + unit: minute diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 0ac7964c3..26a922f6c 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -2,5 +2,5 @@ resources: - kuadrant_v1beta1_kuadrant.yaml - kuadrant_v1beta1_authpolicy.yaml -- kuadrant_v1beta1_ratelimitpolicy.yaml +- kuadrant_v1beta2_ratelimitpolicy.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/controllers/gateway_rlp_eventmapper.go b/controllers/gateway_rlp_eventmapper.go index 4157463df..f477a0f17 100644 --- a/controllers/gateway_rlp_eventmapper.go +++ b/controllers/gateway_rlp_eventmapper.go @@ -10,7 +10,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "github.com/kuadrant/kuadrant-operator/pkg/common" ) @@ -22,7 +22,7 @@ type GatewayRateLimitPolicyEventMapper struct { } func (h *GatewayRateLimitPolicyEventMapper) MapRouteRateLimitPolicy(obj client.Object) []reconcile.Request { - rlp, ok := obj.(*kuadrantv1beta1.RateLimitPolicy) + rlp, ok := obj.(*kuadrantv1beta2.RateLimitPolicy) if !ok { h.Logger.V(1).Info("MapRouteRateLimitPolicy: RLP not received", "error", fmt.Sprintf("%T is not a *kuadrantv1beta1.RateLimitPolicy", obj)) return []reconcile.Request{} diff --git a/controllers/ratelimitpolicy_cluster_envoy_filter.go b/controllers/ratelimitpolicy_cluster_envoy_filter.go index fac83a052..1157d76fe 100644 --- a/controllers/ratelimitpolicy_cluster_envoy_filter.go +++ b/controllers/ratelimitpolicy_cluster_envoy_filter.go @@ -14,13 +14,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "github.com/kuadrant/kuadrant-operator/pkg/common" kuadrantistioutils "github.com/kuadrant/kuadrant-operator/pkg/istio" "github.com/kuadrant/kuadrant-operator/pkg/reconcilers" ) -func (r *RateLimitPolicyReconciler) reconcileRateLimitingClusterEnvoyFilter(ctx context.Context, rlp *kuadrantv1beta1.RateLimitPolicy, gwDiffObj *reconcilers.GatewayDiff) error { +func (r *RateLimitPolicyReconciler) reconcileRateLimitingClusterEnvoyFilter(ctx context.Context, rlp *kuadrantv1beta2.RateLimitPolicy, gwDiffObj *reconcilers.GatewayDiff) error { logger, _ := logr.FromContext(ctx) for _, gw := range gwDiffObj.GatewaysWithInvalidPolicyRef { diff --git a/controllers/ratelimitpolicy_controller.go b/controllers/ratelimitpolicy_controller.go index f8005f692..8c94a400a 100644 --- a/controllers/ratelimitpolicy_controller.go +++ b/controllers/ratelimitpolicy_controller.go @@ -29,7 +29,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "github.com/kuadrant/kuadrant-operator/pkg/common" "github.com/kuadrant/kuadrant-operator/pkg/reconcilers" ) @@ -65,7 +65,7 @@ func (r *RateLimitPolicyReconciler) Reconcile(eventCtx context.Context, req ctrl ctx := logr.NewContext(eventCtx, logger) // fetch the ratelimitpolicy - rlp := &kuadrantv1beta1.RateLimitPolicy{} + rlp := &kuadrantv1beta2.RateLimitPolicy{} if err := r.Client().Get(ctx, req.NamespacedName, rlp); err != nil { if apierrors.IsNotFound(err) { logger.Info("no RateLimitPolicy found") @@ -95,7 +95,7 @@ func (r *RateLimitPolicyReconciler) Reconcile(eventCtx context.Context, req ctrl if delResErr == nil { delResErr = err } - return r.reconcileStatus(ctx, rlp, nil, delResErr) + return r.reconcileStatus(ctx, rlp, delResErr) } return ctrl.Result{}, err } @@ -132,7 +132,7 @@ func (r *RateLimitPolicyReconciler) Reconcile(eventCtx context.Context, req ctrl specErr := r.reconcileResources(ctx, rlp, targetNetworkObject) // reconcile ratelimitpolicy status - statusResult, statusErr := r.reconcileStatus(ctx, rlp, targetNetworkObject, specErr) + statusResult, statusErr := r.reconcileStatus(ctx, rlp, specErr) if specErr != nil { return ctrl.Result{}, specErr @@ -151,7 +151,7 @@ func (r *RateLimitPolicyReconciler) Reconcile(eventCtx context.Context, req ctrl return ctrl.Result{}, nil } -func (r *RateLimitPolicyReconciler) reconcileResources(ctx context.Context, rlp *kuadrantv1beta1.RateLimitPolicy, targetNetworkObject client.Object) error { +func (r *RateLimitPolicyReconciler) reconcileResources(ctx context.Context, rlp *kuadrantv1beta2.RateLimitPolicy, targetNetworkObject client.Object) error { // validate err := rlp.Validate() if err != nil { @@ -169,7 +169,7 @@ func (r *RateLimitPolicyReconciler) reconcileResources(ctx context.Context, rlp return err } - if err := r.reconcileLimits(ctx, rlp, gatewayDiffObj); err != nil { + if err := r.reconcileLimits(ctx, rlp); err != nil { return err } @@ -190,7 +190,7 @@ func (r *RateLimitPolicyReconciler) reconcileResources(ctx context.Context, rlp return r.ReconcileGatewayPolicyReferences(ctx, rlp, gatewayDiffObj) } -func (r *RateLimitPolicyReconciler) deleteResources(ctx context.Context, rlp *kuadrantv1beta1.RateLimitPolicy, targetNetworkObject client.Object) error { +func (r *RateLimitPolicyReconciler) deleteResources(ctx context.Context, rlp *kuadrantv1beta2.RateLimitPolicy, targetNetworkObject client.Object) error { // delete based on gateway diffs gatewayDiffObj, err := r.ComputeGatewayDiffs(ctx, rlp, targetNetworkObject, &common.KuadrantRateLimitPolicyRefsConfig{}) if err != nil { @@ -205,7 +205,7 @@ func (r *RateLimitPolicyReconciler) deleteResources(ctx context.Context, rlp *ku return err } - if err := r.reconcileLimits(ctx, rlp, gatewayDiffObj); err != nil && !apierrors.IsNotFound(err) { + if err := r.deleteLimits(ctx, rlp); err != nil && !apierrors.IsNotFound(err) { return err } @@ -225,7 +225,7 @@ func (r *RateLimitPolicyReconciler) reconcileNetworkResourceDirectBackReference( return r.ReconcileTargetBackReference(ctx, client.ObjectKeyFromObject(policy), targetNetworkObject, common.RateLimitPolicyBackRefAnnotation) } -func (r *RateLimitPolicyReconciler) deleteNetworkResourceDirectBackReference(ctx context.Context, rlp *kuadrantv1beta1.RateLimitPolicy, targetNetworkObject client.Object) error { +func (r *RateLimitPolicyReconciler) deleteNetworkResourceDirectBackReference(ctx context.Context, rlp *kuadrantv1beta2.RateLimitPolicy, targetNetworkObject client.Object) error { return r.DeleteTargetBackReference(ctx, client.ObjectKeyFromObject(rlp), targetNetworkObject, common.RateLimitPolicyBackRefAnnotation) } @@ -242,7 +242,7 @@ func (r *RateLimitPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { Client: r.Client(), } return ctrl.NewControllerManagedBy(mgr). - For(&kuadrantv1beta1.RateLimitPolicy{}). + For(&kuadrantv1beta2.RateLimitPolicy{}). Watches( &source.Kind{Type: &gatewayapiv1beta1.HTTPRoute{}}, handler.EnqueueRequestsFromMapFunc(httpRouteEventMapper.MapToRateLimitPolicy), @@ -255,7 +255,7 @@ func (r *RateLimitPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { ). // When gateway level RLP changes, notify route level RLP's Watches( - &source.Kind{Type: &kuadrantv1beta1.RateLimitPolicy{}}, + &source.Kind{Type: &kuadrantv1beta2.RateLimitPolicy{}}, handler.EnqueueRequestsFromMapFunc(gatewayRateLimtPolicyEventMapper.MapRouteRateLimitPolicy), ). Complete(r) diff --git a/controllers/ratelimitpolicy_controller_test.go b/controllers/ratelimitpolicy_controller_test.go index 9252a5b0f..7c5b155c1 100644 --- a/controllers/ratelimitpolicy_controller_test.go +++ b/controllers/ratelimitpolicy_controller_test.go @@ -13,6 +13,7 @@ import ( istioclientgoextensionv1alpha1 "istio.io/client-go/pkg/apis/extensions/v1alpha1" istioclientnetworkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -20,9 +21,10 @@ import ( gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "github.com/kuadrant/kuadrant-operator/pkg/common" "github.com/kuadrant/kuadrant-operator/pkg/rlptools" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" ) @@ -51,17 +53,7 @@ func testBuildBasicGateway(gwName, ns string) *gatewayapiv1beta1.Gateway { } } -func testBuildBasicHttpRoute(routeName, gwName, ns string, hostnamesStrSlice []string) *gatewayapiv1beta1.HTTPRoute { - tmpMatchPathPrefix := gatewayapiv1beta1.PathMatchPathPrefix - tmpMatchValue := "/toy" - tmpMatchMethod := gatewayapiv1beta1.HTTPMethod("GET") - gwNamespace := gatewayapiv1beta1.Namespace(ns) - - var hostnames []gatewayapiv1beta1.Hostname - for _, str := range hostnamesStrSlice { - hostnames = append(hostnames, gatewayapiv1beta1.Hostname(str)) - } - +func testBuildBasicHttpRoute(routeName, gwName, ns string, hostnames []string) *gatewayapiv1beta1.HTTPRoute { return &gatewayapiv1beta1.HTTPRoute{ TypeMeta: metav1.TypeMeta{ Kind: "HTTPRoute", @@ -77,112 +69,20 @@ func testBuildBasicHttpRoute(routeName, gwName, ns string, hostnamesStrSlice []s ParentRefs: []gatewayapiv1beta1.ParentReference{ { Name: gatewayapiv1beta1.ObjectName(gwName), - Namespace: &gwNamespace, + Namespace: common.Ptr(gatewayapiv1beta1.Namespace(ns)), }, }, }, - Hostnames: hostnames, + Hostnames: common.Map(hostnames, func(hostname string) gatewayapiv1beta1.Hostname { return gatewayapiv1beta1.Hostname(hostname) }), Rules: []gatewayapiv1beta1.HTTPRouteRule{ { Matches: []gatewayapiv1beta1.HTTPRouteMatch{ { Path: &gatewayapiv1beta1.HTTPPathMatch{ - Type: &tmpMatchPathPrefix, - Value: &tmpMatchValue, - }, - Method: &tmpMatchMethod, - }, - }, - }, - }, - }, - } -} - -func testBuildBasicRoutePolicy(policyName, ns, routeName string) *kuadrantv1beta1.RateLimitPolicy { - genericDescriptorKey := "op" - - return &kuadrantv1beta1.RateLimitPolicy{ - TypeMeta: metav1.TypeMeta{ - Kind: "RateLimitPolicy", - APIVersion: kuadrantv1beta1.GroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: policyName, - Namespace: ns, - }, - Spec: kuadrantv1beta1.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ - Group: gatewayapiv1beta1.Group("gateway.networking.k8s.io"), - Kind: "HTTPRoute", - Name: gatewayapiv1beta1.ObjectName(routeName), - }, - RateLimits: []kuadrantv1beta1.RateLimit{ - { - Configurations: []kuadrantv1beta1.Configuration{ - { - Actions: []kuadrantv1beta1.ActionSpecifier{ - { - GenericKey: &kuadrantv1beta1.GenericKeySpec{ - DescriptorValue: "1", - DescriptorKey: &genericDescriptorKey, - }, - }, - }, - }, - }, - Limits: []kuadrantv1beta1.Limit{ - { - MaxValue: 5, - Seconds: 10, - Conditions: []string{"op == 1"}, - Variables: []string{}, - }, - }, - }, - }, - }, - } -} - -func testBuildGatewayPolicy(policyName, ns, gwName string) *kuadrantv1beta1.RateLimitPolicy { - genericDescriptorKey := "op" - - return &kuadrantv1beta1.RateLimitPolicy{ - TypeMeta: metav1.TypeMeta{ - Kind: "RateLimitPolicy", - APIVersion: kuadrantv1beta1.GroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: policyName, - Namespace: ns, - }, - Spec: kuadrantv1beta1.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ - Group: gatewayapiv1beta1.Group("gateway.networking.k8s.io"), - Kind: "Gateway", - Name: gatewayapiv1beta1.ObjectName(gwName), - }, - RateLimits: []kuadrantv1beta1.RateLimit{ - { - Configurations: []kuadrantv1beta1.Configuration{ - { - Actions: []kuadrantv1beta1.ActionSpecifier{ - { - GenericKey: &kuadrantv1beta1.GenericKeySpec{ - DescriptorValue: "1", - DescriptorKey: &genericDescriptorKey, - }, - }, + Type: common.Ptr(gatewayapiv1beta1.PathMatchPathPrefix), + Value: common.Ptr("/toy"), }, - }, - }, - Limits: []kuadrantv1beta1.Limit{ - { - MaxValue: 5, - Seconds: 10, - Conditions: []string{"op == 1"}, - Variables: []string{}, + Method: common.Ptr(gatewayapiv1beta1.HTTPMethod("GET")), }, }, }, @@ -193,12 +93,11 @@ func testBuildGatewayPolicy(policyName, ns, gwName string) *kuadrantv1beta1.Rate var _ = Describe("RateLimitPolicy controller", func() { var ( - testNamespace string - genericDescriptorKey string = "op" - routeName = "toystore-route" - gwName = "toystore-gw" - rlpName = "toystore-rlp" - gateway *gatewayapiv1beta1.Gateway + testNamespace string + routeName = "toystore-route" + gwName = "toystore-gw" + rlpName = "toystore-rlp" + gateway *gatewayapiv1beta1.Gateway ) beforeEachCallback := func() { @@ -224,48 +123,64 @@ var _ = Describe("RateLimitPolicy controller", func() { }, 15*time.Second, 5*time.Second).Should(BeTrue()) ApplyKuadrantCR(testNamespace) + + // Check Limitador Status is Ready + Eventually(func() bool { + limitador := &limitadorv1alpha1.Limitador{} + err := k8sClient.Get(context.Background(), client.ObjectKey{Name: common.LimitadorName, Namespace: testNamespace}, limitador) + if err != nil { + return false + } + if !meta.IsStatusConditionTrue(limitador.Status.Conditions, "Ready") { + return false + } + return true + }, time.Minute, 5*time.Second).Should(BeTrue()) } BeforeEach(beforeEachCallback) AfterEach(DeleteNamespaceCallback(&testNamespace)) - Context("Basic: RLP targeting HTTPRoute", func() { - It("check created resources", func() { - // Check Limitador Status is Ready - Eventually(func() bool { - limitador := &limitadorv1alpha1.Limitador{} - err := k8sClient.Get(context.Background(), client.ObjectKey{Name: common.LimitadorName, Namespace: testNamespace}, limitador) - if err != nil { - return false - } - if !meta.IsStatusConditionTrue(limitador.Status.Conditions, "Ready") { - return false - } - return true - }, time.Minute, 5*time.Second).Should(BeTrue()) - + Context("RLP targeting HTTPRoute", func() { + It("Creates all the resources for a basic HTTPRoute and RateLimitPolicy", func() { + // create httproute httpRoute := testBuildBasicHttpRoute(routeName, gwName, testNamespace, []string{"*.example.com"}) err := k8sClient.Create(context.Background(), httpRoute) Expect(err).ToNot(HaveOccurred()) - rlp := testBuildBasicRoutePolicy(rlpName, testNamespace, routeName) - rlpKey := client.ObjectKey{Name: rlpName, Namespace: testNamespace} + // create ratelimitpolicy + rlp := &kuadrantv1beta2.RateLimitPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "RateLimitPolicy", + APIVersion: kuadrantv1beta2.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: rlpName, + Namespace: testNamespace, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: gatewayapiv1beta1.Group("gateway.networking.k8s.io"), + Kind: "HTTPRoute", + Name: gatewayapiv1beta1.ObjectName(routeName), + }, + Limits: map[string]kuadrantv1beta2.Limit{ + "l1": { + Rates: []kuadrantv1beta2.Rate{ + { + Limit: 1, Duration: 3, Unit: kuadrantv1beta2.TimeUnit("minute"), + }, + }, + }, + }, + }, + } err = k8sClient.Create(context.Background(), rlp) Expect(err).ToNot(HaveOccurred()) // Check RLP status is available - Eventually(func() bool { - existingRLP := &kuadrantv1beta1.RateLimitPolicy{} - err := k8sClient.Get(context.Background(), rlpKey, existingRLP) - if err != nil { - return false - } - if !meta.IsStatusConditionTrue(existingRLP.Status.Conditions, "Available") { - return false - } - - return true - }, time.Minute, 5*time.Second).Should(BeTrue()) + rlpKey := client.ObjectKeyFromObject(rlp) + Eventually(testRLPIsAvailable(rlpKey), time.Minute, 5*time.Second).Should(BeTrue()) // Check HTTPRoute direct back reference routeKey := client.ObjectKey{Name: routeName, Namespace: testNamespace} @@ -283,10 +198,10 @@ var _ = Describe("RateLimitPolicy controller", func() { // must exist Expect(err).ToNot(HaveOccurred()) Expect(existingLimitador.Spec.Limits).To(ContainElements(limitadorv1alpha1.RateLimit{ - MaxValue: 5, - Seconds: 10, - Namespace: common.MarshallNamespace(client.ObjectKeyFromObject(gateway), "*.example.com"), - Conditions: []string{"op == 1"}, + MaxValue: 1, + Seconds: 3 * 60, + Namespace: rlptools.LimitsNamespaceFromRLP(rlp), + Conditions: []string{`limit.l1__2804bad6 == "1"`}, Variables: []string{}, })) @@ -307,37 +222,42 @@ var _ = Describe("RateLimitPolicy controller", func() { Expect(err).ToNot(HaveOccurred()) existingWASMConfig, err := rlptools.WASMPluginFromStruct(existingWasmPlugin.Spec.PluginConfig) Expect(err).ToNot(HaveOccurred()) - Expect(existingWASMConfig).To(Equal(&rlptools.WASMPlugin{ - FailureModeDeny: true, - RateLimitPolicies: []rlptools.RateLimitPolicy{ + Expect(existingWASMConfig).To(Equal(&wasm.Plugin{ + FailureMode: wasm.FailureModeDeny, + RateLimitPolicies: []wasm.RateLimitPolicy{ { - Name: "*.example.com", - RateLimitDomain: common.MarshallNamespace(client.ObjectKeyFromObject(gateway), "*.example.com"), - UpstreamCluster: common.KuadrantRateLimitClusterName, - Hostnames: []string{"*.example.com"}, - GatewayActions: []rlptools.GatewayAction{ + Name: rlpKey.String(), + Domain: rlptools.LimitsNamespaceFromRLP(rlp), + Rules: []wasm.Rule{ { - Rules: []kuadrantv1beta1.Rule{ + Conditions: []wasm.Condition{ { - Hosts: []string{"*.example.com"}, - Paths: []string{"/toy*"}, - Methods: []string{"GET"}, + AllOf: []wasm.PatternExpression{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), + Value: "GET", + }, + }, }, }, - Configurations: []kuadrantv1beta1.Configuration{ + Data: []wasm.DataItem{ { - Actions: []kuadrantv1beta1.ActionSpecifier{ - { - GenericKey: &kuadrantv1beta1.GenericKeySpec{ - DescriptorValue: "1", - DescriptorKey: &genericDescriptorKey, - }, - }, + Static: &wasm.StaticSpec{ + Key: `limit.l1__2804bad6`, + Value: "1", }, }, }, }, }, + Hostnames: []string{"*.example.com"}, + Service: common.KuadrantRateLimitClusterName, }, }, })) @@ -354,86 +274,113 @@ var _ = Describe("RateLimitPolicy controller", func() { Expect(existingGateway.GetAnnotations()).To(HaveKeyWithValue( common.RateLimitPoliciesBackRefAnnotation, string(serialized))) }) - }) - Context("Basic: Simplest RLP targeting HTTPRoute", func() { - It("check created resources", func() { - // Check Limitador Status is Ready - Eventually(func() bool { - limitador := &limitadorv1alpha1.Limitador{} - err := k8sClient.Get(context.Background(), client.ObjectKey{Name: common.LimitadorName, Namespace: testNamespace}, limitador) - if err != nil { - return false - } - if !meta.IsStatusConditionTrue(limitador.Status.Conditions, "Ready") { - return false - } - return true - }, time.Minute, 5*time.Second).Should(BeTrue()) - - httpRoute := testBuildBasicHttpRoute(routeName, gwName, testNamespace, []string{"*.example.com"}) + It("Creates the correct WasmPlugin for a complex HTTPRoute and a RateLimitPolicy", func() { + // create httproute + httpRoute := testBuildBasicHttpRoute(routeName, gwName, testNamespace, []string{"*.toystore.acme.com", "api.toystore.io"}) + httpRoute.Spec.Rules = []gatewayapiv1beta1.HTTPRouteRule{ + { + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { // get /toys* + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: common.Ptr(gatewayapiv1beta1.PathMatchPathPrefix), + Value: common.Ptr("/toys"), + }, + Method: common.Ptr(gatewayapiv1beta1.HTTPMethod("GET")), + }, + { // post /toys* + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: common.Ptr(gatewayapiv1beta1.PathMatchPathPrefix), + Value: common.Ptr("/toys"), + }, + Method: common.Ptr(gatewayapiv1beta1.HTTPMethod("POST")), + }, + }, + }, + { + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { // /assets* + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: common.Ptr(gatewayapiv1beta1.PathMatchPathPrefix), + Value: common.Ptr("/assets"), + }, + }, + }, + }, + } err := k8sClient.Create(context.Background(), httpRoute) Expect(err).ToNot(HaveOccurred()) - rlp := &kuadrantv1beta1.RateLimitPolicy{ + // create ratelimitpolicy + rlp := &kuadrantv1beta2.RateLimitPolicy{ TypeMeta: metav1.TypeMeta{ Kind: "RateLimitPolicy", - APIVersion: kuadrantv1beta1.GroupVersion.String(), + APIVersion: kuadrantv1beta2.GroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ Name: rlpName, Namespace: testNamespace, }, - Spec: kuadrantv1beta1.RateLimitPolicySpec{ + Spec: kuadrantv1beta2.RateLimitPolicySpec{ TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ Group: gatewayapiv1beta1.Group("gateway.networking.k8s.io"), Kind: "HTTPRoute", Name: gatewayapiv1beta1.ObjectName(routeName), }, - RateLimits: []kuadrantv1beta1.RateLimit{ - { - Limits: []kuadrantv1beta1.Limit{ + Limits: map[string]kuadrantv1beta2.Limit{ + "toys": { + Rates: []kuadrantv1beta2.Rate{ + {Limit: 50, Duration: 1, Unit: kuadrantv1beta2.TimeUnit("minute")}, + }, + Counters: []kuadrantv1beta2.ContextSelector{"auth.identity.username"}, + RouteSelectors: []kuadrantv1beta2.RouteSelector{ + { // selects the 1st HTTPRouteRule (i.e. get|post /toys*) for one of the hostnames + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: common.Ptr(gatewayapiv1beta1.PathMatchPathPrefix), + Value: common.Ptr("/toys"), + }, + }, + }, + Hostnames: []gatewayapiv1beta1.Hostname{"*.toystore.acme.com"}, + }, + }, + When: []kuadrantv1beta2.WhenCondition{ { - MaxValue: 5, - Seconds: 10, + Selector: "auth.identity.group", + Operator: kuadrantv1beta2.WhenConditionOperator("neq"), + Value: "admin", + }, + }, + }, + "assets": { + Rates: []kuadrantv1beta2.Rate{ + {Limit: 5, Duration: 1, Unit: kuadrantv1beta2.TimeUnit("minute")}, + {Limit: 100, Duration: 12, Unit: kuadrantv1beta2.TimeUnit("hour")}, + }, + RouteSelectors: []kuadrantv1beta2.RouteSelector{ + { // selects the 2nd HTTPRouteRule (i.e. /assets*) for all hostnames + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: common.Ptr(gatewayapiv1beta1.PathMatchPathPrefix), + Value: common.Ptr("/assets"), + }, + }, + }, }, }, }, }, }, } - - rlpKey := client.ObjectKey{Name: rlpName, Namespace: testNamespace} err = k8sClient.Create(context.Background(), rlp) Expect(err).ToNot(HaveOccurred()) // Check RLP status is available - Eventually(func() bool { - existingRLP := &kuadrantv1beta1.RateLimitPolicy{} - err := k8sClient.Get(context.Background(), rlpKey, existingRLP) - if err != nil { - return false - } - if !meta.IsStatusConditionTrue(existingRLP.Status.Conditions, "Available") { - return false - } - - return true - }, time.Minute, 5*time.Second).Should(BeTrue()) - - // check limits - limitadorKey := client.ObjectKey{Name: common.LimitadorName, Namespace: testNamespace} - existingLimitador := &limitadorv1alpha1.Limitador{} - err = k8sClient.Get(context.Background(), limitadorKey, existingLimitador) - // must exist - Expect(err).ToNot(HaveOccurred()) - Expect(existingLimitador.Spec.Limits).To(ContainElements(limitadorv1alpha1.RateLimit{ - MaxValue: 5, - Seconds: 10, - Namespace: common.MarshallNamespace(client.ObjectKeyFromObject(gateway), "*.example.com"), - Conditions: []string{}, - Variables: []string{}, - })) + rlpKey := client.ObjectKeyFromObject(rlp) + Eventually(testRLPIsAvailable(rlpKey), time.Minute, 5*time.Second).Should(BeTrue()) // Check wasm plugin wpName := fmt.Sprintf("kuadrant-%s", gwName) @@ -444,76 +391,142 @@ var _ = Describe("RateLimitPolicy controller", func() { Expect(err).ToNot(HaveOccurred()) existingWASMConfig, err := rlptools.WASMPluginFromStruct(existingWasmPlugin.Spec.PluginConfig) Expect(err).ToNot(HaveOccurred()) - Expect(existingWASMConfig).To(Equal(&rlptools.WASMPlugin{ - FailureModeDeny: true, - RateLimitPolicies: []rlptools.RateLimitPolicy{ + Expect(existingWASMConfig.FailureMode).To(Equal(wasm.FailureModeDeny)) + Expect(existingWASMConfig.RateLimitPolicies).To(HaveLen(1)) + wasmRLP := existingWASMConfig.RateLimitPolicies[0] + Expect(wasmRLP.Name).To(Equal(rlpKey.String())) + Expect(wasmRLP.Domain).To(Equal(rlptools.LimitsNamespaceFromRLP(rlp))) + Expect(wasmRLP.Rules).To(ContainElement(wasm.Rule{ // rule to activate the 'toys' limit definition + Conditions: []wasm.Condition{ { - Name: "*.example.com", - RateLimitDomain: common.MarshallNamespace(client.ObjectKeyFromObject(gateway), "*.example.com"), - UpstreamCluster: common.KuadrantRateLimitClusterName, - Hostnames: []string{"*.example.com"}, - GatewayActions: []rlptools.GatewayAction{ + AllOf: []wasm.PatternExpression{ { - Rules: []kuadrantv1beta1.Rule{ - { - Paths: []string{"/toy*"}, - Methods: []string{"GET"}, - Hosts: []string{"*.example.com"}, - }, - }, - Configurations: []kuadrantv1beta1.Configuration{ - { - Actions: []kuadrantv1beta1.ActionSpecifier{ - { - GenericKey: &kuadrantv1beta1.GenericKeySpec{ - DescriptorValue: rlpKey.String(), - DescriptorKey: &[]string{"ratelimitpolicy"}[0], - }, - }, - }, - }, - }, + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), + Value: "/toys", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), + Value: "GET", + }, + { + Selector: "request.host", + Operator: wasm.PatternOperator(kuadrantv1beta2.EndsWithOperator), + Value: ".toystore.acme.com", + }, + { + Selector: "auth.identity.group", + Operator: wasm.PatternOperator(kuadrantv1beta2.NotEqualOperator), + Value: "admin", + }, + }, + }, + { + AllOf: []wasm.PatternExpression{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), + Value: "/toys", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), + Value: "POST", + }, + { + Selector: "request.host", + Operator: wasm.PatternOperator(kuadrantv1beta2.EndsWithOperator), + Value: ".toystore.acme.com", + }, + { + Selector: "auth.identity.group", + Operator: wasm.PatternOperator(kuadrantv1beta2.NotEqualOperator), + Value: "admin", }, }, }, }, + Data: []wasm.DataItem{ + { + Static: &wasm.StaticSpec{ + Key: "limit.toys__3bfcbeee", + Value: "1", + }, + }, + { + Selector: &wasm.SelectorSpec{ + Selector: kuadrantv1beta2.ContextSelector("auth.identity.username"), + }, + }, + }, })) + Expect(wasmRLP.Rules).To(ContainElement(wasm.Rule{ // rule to activate the 'assets' limit definition + Conditions: []wasm.Condition{ + { + AllOf: []wasm.PatternExpression{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), + Value: "/assets", + }, + }, + }, + }, + Data: []wasm.DataItem{ + { + Static: &wasm.StaticSpec{ + Key: "limit.assets__8bf729ff", + Value: "1", + }, + }, + }, + })) + Expect(wasmRLP.Hostnames).To(Equal([]string{"*.toystore.acme.com", "api.toystore.io"})) + Expect(wasmRLP.Service).To(Equal(common.KuadrantRateLimitClusterName)) }) }) - Context("Basic: RLP targeting Gateway", func() { - It("check created resources", func() { - // Check Limitador Status is Ready - Eventually(func() bool { - limitador := &limitadorv1alpha1.Limitador{} - err := k8sClient.Get(context.Background(), client.ObjectKey{Name: common.LimitadorName, Namespace: testNamespace}, limitador) - if err != nil { - return false - } - if !meta.IsStatusConditionTrue(limitador.Status.Conditions, "Ready") { - return false - } - return true - }, time.Minute, 5*time.Second).Should(BeTrue()) - - rlp := testBuildGatewayPolicy(rlpName, testNamespace, gwName) - rlpKey := client.ObjectKey{Name: rlpName, Namespace: testNamespace} - err := k8sClient.Create(context.Background(), rlp) + Context("RLP targeting Gateway", func() { + It("Creates all the resources for a basic Gateway and RateLimitPolicy", func() { + // create httproute + httpRoute := testBuildBasicHttpRoute(routeName, gwName, testNamespace, []string{"*.example.com"}) + err := k8sClient.Create(context.Background(), httpRoute) + Expect(err).ToNot(HaveOccurred()) + + // create ratelimitpolicy + rlp := &kuadrantv1beta2.RateLimitPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "RateLimitPolicy", + APIVersion: kuadrantv1beta2.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: rlpName, + Namespace: testNamespace, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: gatewayapiv1beta1.Group("gateway.networking.k8s.io"), + Kind: "Gateway", + Name: gatewayapiv1beta1.ObjectName(gwName), + }, + Limits: map[string]kuadrantv1beta2.Limit{ + "l1": { + Rates: []kuadrantv1beta2.Rate{ + { + Limit: 1, Duration: 3, Unit: kuadrantv1beta2.TimeUnit("minute"), + }, + }, + }, + }, + }, + } + err = k8sClient.Create(context.Background(), rlp) Expect(err).ToNot(HaveOccurred()) // Check RLP status is available - Eventually(func() bool { - existingRLP := &kuadrantv1beta1.RateLimitPolicy{} - err := k8sClient.Get(context.Background(), rlpKey, existingRLP) - if err != nil { - return false - } - if !meta.IsStatusConditionTrue(existingRLP.Status.Conditions, "Available") { - return false - } - - return true - }, time.Minute, 5*time.Second).Should(BeTrue()) + rlpKey := client.ObjectKey{Name: rlpName, Namespace: testNamespace} + Eventually(testRLPIsAvailable(rlpKey), time.Minute, 5*time.Second).Should(BeTrue()) // Check Gateway direct back reference gwKey := client.ObjectKeyFromObject(gateway) @@ -531,10 +544,10 @@ var _ = Describe("RateLimitPolicy controller", func() { // must exist Expect(err).ToNot(HaveOccurred()) Expect(existingLimitador.Spec.Limits).To(ContainElements(limitadorv1alpha1.RateLimit{ - MaxValue: 5, - Seconds: 10, - Namespace: common.MarshallNamespace(client.ObjectKeyFromObject(gateway), "*"), - Conditions: []string{"op == 1"}, + MaxValue: 1, + Seconds: 3 * 60, + Namespace: rlptools.LimitsNamespaceFromRLP(rlp), + Conditions: []string{`limit.l1__2804bad6 == "1"`}, Variables: []string{}, })) @@ -555,30 +568,42 @@ var _ = Describe("RateLimitPolicy controller", func() { Expect(err).ToNot(HaveOccurred()) existingWASMConfig, err := rlptools.WASMPluginFromStruct(existingWasmPlugin.Spec.PluginConfig) Expect(err).ToNot(HaveOccurred()) - Expect(existingWASMConfig).To(Equal(&rlptools.WASMPlugin{ - FailureModeDeny: true, - RateLimitPolicies: []rlptools.RateLimitPolicy{ + Expect(existingWASMConfig).To(Equal(&wasm.Plugin{ + FailureMode: wasm.FailureModeDeny, + RateLimitPolicies: []wasm.RateLimitPolicy{ { - Name: "*", - RateLimitDomain: common.MarshallNamespace(client.ObjectKeyFromObject(gateway), "*"), - UpstreamCluster: common.KuadrantRateLimitClusterName, - Hostnames: []string{"*"}, - GatewayActions: []rlptools.GatewayAction{ + Name: rlpKey.String(), + Domain: rlptools.LimitsNamespaceFromRLP(rlp), + Rules: []wasm.Rule{ { - Configurations: []kuadrantv1beta1.Configuration{ + Conditions: []wasm.Condition{ { - Actions: []kuadrantv1beta1.ActionSpecifier{ + AllOf: []wasm.PatternExpression{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), + Value: "/toy", + }, { - GenericKey: &kuadrantv1beta1.GenericKeySpec{ - DescriptorValue: "1", - DescriptorKey: &genericDescriptorKey, - }, + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), + Value: "GET", }, }, }, }, + Data: []wasm.DataItem{ + { + Static: &wasm.StaticSpec{ + Key: `limit.l1__2804bad6`, + Value: "1", + }, + }, + }, }, }, + Hostnames: []string{"*"}, + Service: common.KuadrantRateLimitClusterName, }, }, })) @@ -590,8 +615,106 @@ var _ = Describe("RateLimitPolicy controller", func() { refs := []client.ObjectKey{rlpKey} serialized, err := json.Marshal(refs) Expect(err).ToNot(HaveOccurred()) + Expect(existingGateway.GetAnnotations()).To(HaveKeyWithValue(common.RateLimitPoliciesBackRefAnnotation, string(serialized))) + }) + + It("Creates all the resources for a basic Gateway and RateLimitPolicy when missing a HTTPRoute attached to the Gateway", func() { + // create ratelimitpolicy + rlp := &kuadrantv1beta2.RateLimitPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "RateLimitPolicy", + APIVersion: kuadrantv1beta2.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: rlpName, + Namespace: testNamespace, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: gatewayapiv1beta1.Group("gateway.networking.k8s.io"), + Kind: "Gateway", + Name: gatewayapiv1beta1.ObjectName(gwName), + }, + Limits: map[string]kuadrantv1beta2.Limit{ + "l1": { + Rates: []kuadrantv1beta2.Rate{ + { + Limit: 1, Duration: 3, Unit: kuadrantv1beta2.TimeUnit("minute"), + }, + }, + }, + }, + }, + } + err := k8sClient.Create(context.Background(), rlp) + Expect(err).ToNot(HaveOccurred()) + + // Check RLP status is available + rlpKey := client.ObjectKey{Name: rlpName, Namespace: testNamespace} + Eventually(testRLPIsAvailable(rlpKey), time.Minute, 5*time.Second).Should(BeTrue()) + + // Check Gateway direct back reference + gwKey := client.ObjectKeyFromObject(gateway) + existingGateway := &gatewayapiv1beta1.Gateway{} + err = k8sClient.Get(context.Background(), gwKey, existingGateway) + // must exist + Expect(err).ToNot(HaveOccurred()) Expect(existingGateway.GetAnnotations()).To(HaveKeyWithValue( - common.RateLimitPoliciesBackRefAnnotation, string(serialized))) + common.RateLimitPolicyBackRefAnnotation, client.ObjectKeyFromObject(rlp).String())) + + // check limits + limitadorKey := client.ObjectKey{Name: common.LimitadorName, Namespace: testNamespace} + existingLimitador := &limitadorv1alpha1.Limitador{} + err = k8sClient.Get(context.Background(), limitadorKey, existingLimitador) + // must exist + Expect(err).ToNot(HaveOccurred()) + Expect(existingLimitador.Spec.Limits).To(ContainElements(limitadorv1alpha1.RateLimit{ + MaxValue: 1, + Seconds: 3 * 60, + Namespace: rlptools.LimitsNamespaceFromRLP(rlp), + Conditions: []string{`limit.l1__2804bad6 == "1"`}, + Variables: []string{}, + })) + + // Check envoy filter + efName := fmt.Sprintf("kuadrant-ratelimiting-cluster-%s", gwName) + efKey := client.ObjectKey{Name: efName, Namespace: testNamespace} + existingEF := &istioclientnetworkingv1alpha3.EnvoyFilter{} + err = k8sClient.Get(context.Background(), efKey, existingEF) + // must exist + Expect(err).ToNot(HaveOccurred()) + + // Check wasm plugin + wpName := fmt.Sprintf("kuadrant-%s", gwName) + wasmPluginKey := client.ObjectKey{Name: wpName, Namespace: testNamespace} + existingWasmPlugin := &istioclientgoextensionv1alpha1.WasmPlugin{} + // must not exist + err = k8sClient.Get(context.Background(), wasmPluginKey, existingWasmPlugin) + Expect(apierrors.IsNotFound(err)).To(BeTrue()) + + // Check gateway back references + err = k8sClient.Get(context.Background(), gwKey, existingGateway) + // must exist + Expect(err).ToNot(HaveOccurred()) + refs := []client.ObjectKey{rlpKey} + serialized, err := json.Marshal(refs) + Expect(err).ToNot(HaveOccurred()) + Expect(existingGateway.GetAnnotations()).To(HaveKeyWithValue(common.RateLimitPoliciesBackRefAnnotation, string(serialized))) }) }) }) + +func testRLPIsAvailable(rlpKey client.ObjectKey) func() bool { + return func() bool { + existingRLP := &kuadrantv1beta2.RateLimitPolicy{} + err := k8sClient.Get(context.Background(), rlpKey, existingRLP) + if err != nil { + return false + } + if !meta.IsStatusConditionTrue(existingRLP.Status.Conditions, "Available") { + return false + } + + return true + } +} diff --git a/controllers/ratelimitpolicy_limits.go b/controllers/ratelimitpolicy_limits.go index 3e8a1e831..1704c74ad 100644 --- a/controllers/ratelimitpolicy_limits.go +++ b/controllers/ratelimitpolicy_limits.go @@ -2,189 +2,109 @@ package controllers import ( "context" - "encoding/json" - "fmt" "github.com/go-logr/logr" limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" "sigs.k8s.io/controller-runtime/pkg/client" - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "github.com/kuadrant/kuadrant-operator/pkg/common" - "github.com/kuadrant/kuadrant-operator/pkg/reconcilers" "github.com/kuadrant/kuadrant-operator/pkg/rlptools" ) -func (r *RateLimitPolicyReconciler) reconcileLimits(ctx context.Context, rlp *kuadrantv1beta1.RateLimitPolicy, gwDiffObj *reconcilers.GatewayDiff) error { +func (r *RateLimitPolicyReconciler) reconcileLimits(ctx context.Context, rlp *kuadrantv1beta2.RateLimitPolicy) error { + rlpRefs, err := r.GetAllGatewayPolicyRefs(ctx, &common.KuadrantRateLimitPolicyRefsConfig{}) + if err != nil { + return err + } + return r.reconcileLimitador(ctx, rlp, append(rlpRefs, client.ObjectKeyFromObject(rlp))) +} + +func (r *RateLimitPolicyReconciler) deleteLimits(ctx context.Context, rlp *kuadrantv1beta2.RateLimitPolicy) error { + rlpRefs, err := r.GetAllGatewayPolicyRefs(ctx, &common.KuadrantRateLimitPolicyRefsConfig{}) + if err != nil { + return err + } + + rlpRefsWithoutRLP := common.Filter(rlpRefs, func(rlpRef client.ObjectKey) bool { + return rlpRef.Name != rlp.Name || rlpRef.Namespace != rlp.Namespace + }) + + return r.reconcileLimitador(ctx, rlp, rlpRefsWithoutRLP) +} + +func (r *RateLimitPolicyReconciler) reconcileLimitador(ctx context.Context, rlp *kuadrantv1beta2.RateLimitPolicy, rlpRefs []client.ObjectKey) error { logger, _ := logr.FromContext(ctx) + logger = logger.WithName("reconcileLimitador").WithValues("rlp refs", common.Map(rlpRefs, func(ref client.ObjectKey) string { return ref.String() })) + + rateLimitIndex, err := r.buildRateLimitIndex(ctx, rlpRefs) + if err != nil { + return err + } - logger.V(1).Info("Getting Kuadrant namespace") + // get the current limitador cr for the kuadrant instance so we can compare if it needs to be updated + logger.V(1).Info("get kuadrant namespace") var kuadrantNamespace string - kuadrantNamespace, isSet := common.GetNamespaceFromPolicy(rlp) + kuadrantNamespace, isSet := common.GetKuadrantNamespaceFromPolicy(rlp) if !isSet { var err error - kuadrantNamespace, err = common.GetNamespaceFromPolicyTargetRef(ctx, r.Client(), rlp) + kuadrantNamespace, err = common.GetKuadrantNamespaceFromPolicyTargetRef(ctx, r.Client(), rlp) if err != nil { - logger.Error(err, "failed to get Kuadrant namespace") + logger.Error(err, "failed to get kuadrant namespace") return err } common.AnnotateObject(rlp, kuadrantNamespace) - err = r.UpdateResource(ctx, rlp) + err = r.UpdateResource(ctx, rlp) // @guicassolato: not sure if this belongs to here if err != nil { logger.Error(err, "failed to update policy, re-queuing") return err } } - limitadorKey := client.ObjectKey{Name: common.LimitadorName, Namespace: kuadrantNamespace} limitador := &limitadorv1alpha1.Limitador{} - err := r.Client().Get(ctx, limitadorKey, limitador) - logger.V(1).Info("reconcileLimits", "get limitador", limitadorKey, "err", err) + err = r.Client().Get(ctx, limitadorKey, limitador) + logger.V(1).Info("get limitador", "limitador", limitadorKey, "err", err) if err != nil { return err } - limitIdx := rlptools.NewLimitadorIndex(limitador, logger) - - for _, gw := range gwDiffObj.GatewaysWithInvalidPolicyRef { - logger.V(1).Info("reconcileLimits: gateway with invalid policy ref", "key", gw.Key()) - limitIdx.DeleteGateway(gw.Key()) - } - - for _, gw := range gwDiffObj.GatewaysWithValidPolicyRef { - logger.V(1).Info("reconcileLimits: gateway with valid policy ref", "rlpRefs", gw.PolicyRefs()) - - gwLimits, err := r.gatewayLimits(ctx, gw, gw.PolicyRefs()) - if err != nil { - return err - } - - // delete first to detect when limits have been deleted. - // For instance, gw A has 3 limits - // one limit has been deleted for gwA (coming from a limit deletion in one RLP) - // gw A has now 2 limits - // Deleting the 3 original limits the resulting index will contain only 2 limits as expected - limitIdx.DeleteGateway(gw.Key()) - limitIdx.AddGatewayLimits(gw.Key(), gwLimits) - } - - for _, gw := range gwDiffObj.GatewaysMissingPolicyRef { - rlpRefs := append(gw.PolicyRefs(), client.ObjectKeyFromObject(rlp)) - logger.V(1).Info("reconcileLimits: gateway missing policy ref", "rlpRefs", rlpRefs) - - gwLimits, err := r.gatewayLimits(ctx, gw, rlpRefs) - if err != nil { - return err - } - - // The gw A had X limits from N RLPs - // now there there are N+1 RLPs - // r.gatewayLimits will compute all the limits for the given gateway with the N+1 RLPs - // the existing limits need to be deleted first, - // otherwise they would be added again and will be duplicated in the index - limitIdx.DeleteGateway(gw.Key()) - limitIdx.AddGatewayLimits(gw.Key(), gwLimits) - } - - // Build a new index with the original content of limitador to compare with the new limits - originalLimitIndex := rlptools.NewLimitadorIndex(limitador, logger) - - if logger.V(1).Enabled() { - jsonData, err := json.MarshalIndent(originalLimitIndex.ToLimits(), "", " ") - if err != nil { - return err - } - logger.V(1).Info("reconcileLimits: original limit index") - logger.V(1).Info(string(jsonData)) - - jsonData, err = json.MarshalIndent(limitIdx.ToLimits(), "", " ") - if err != nil { - return err - } - logger.V(1).Info("reconcileLimits: new limit index") - logger.V(1).Info(string(jsonData)) + // return if limitador is up to date + if rlptools.Equal(rateLimitIndex.ToRateLimits(), limitador.Spec.Limits) { + logger.V(1).Info("limitador is up to date, skipping update") + return nil } - equalIndexes := originalLimitIndex.Equals(limitIdx) - logger.V(1).Info("reconcileLimits", "equal index", equalIndexes) - - if !equalIndexes { - limitador.Spec.Limits = limitIdx.ToLimits() - err := r.UpdateResource(ctx, limitador) - logger.V(1).Info("reconcileLimits: update limitador", "limitador", limitadorKey, "err", err) - if err != nil { - return err - } + // update limitador + limitador.Spec.Limits = rateLimitIndex.ToRateLimits() + err = r.UpdateResource(ctx, limitador) + logger.V(1).Info("update limitador", "limitador", limitadorKey, "err", err) + if err != nil { + return err } return nil } -func (r *RateLimitPolicyReconciler) gatewayLimits(ctx context.Context, - gw common.GatewayWrapper, rlpRefs []client.ObjectKey) (rlptools.LimitsByDomain, error) { +func (r *RateLimitPolicyReconciler) buildRateLimitIndex(ctx context.Context, rlpRefs []client.ObjectKey) (*rlptools.RateLimitIndex, error) { logger, _ := logr.FromContext(ctx) - logger.V(1).Info("gatewayLimits", "gwKey", gw.Key(), "rlpRefs", rlpRefs) + logger = logger.WithName("buildRateLimitIndex").WithValues("ratelimitpolicies", rlpRefs) - // Load all rate limit policies - routeRLPList := make([]*kuadrantv1beta1.RateLimitPolicy, 0) - var gwRLP *kuadrantv1beta1.RateLimitPolicy - for _, rlpKey := range rlpRefs { - rlp := &kuadrantv1beta1.RateLimitPolicy{} - err := r.Client().Get(ctx, rlpKey, rlp) - logger.V(1).Info("gatewayLimits", "get rlp", rlpKey, "err", err) - if err != nil { - return nil, err - } - - if common.IsTargetRefHTTPRoute(rlp.Spec.TargetRef) { - routeRLPList = append(routeRLPList, rlp) - } else if common.IsTargetRefGateway(rlp.Spec.TargetRef) { - if gwRLP == nil { - gwRLP = rlp - } else { - return nil, fmt.Errorf("gatewayLimits: multiple gateway RLP found and only one expected. rlp keys: %v", rlpRefs) - } - } - } + rateLimitIndex := rlptools.NewRateLimitIndex() - limits := rlptools.LimitsByDomain{} - - if gwRLP != nil { - if len(gw.Hostnames()) == 0 { - // wildcard domain - limits["*"] = append(limits["*"], gwRLP.FlattenLimits()...) - } else { - for _, gwHostname := range gw.Hostnames() { - limits[gwHostname] = append(limits[gwHostname], gwRLP.FlattenLimits()...) - } + for _, rlpKey := range rlpRefs { + if _, ok := rateLimitIndex.Get(rlpKey); ok { + continue } - } - for _, httpRouteRLP := range routeRLPList { - httpRoute, err := r.FetchValidHTTPRoute(ctx, httpRouteRLP.TargetKey()) + rlp := &kuadrantv1beta2.RateLimitPolicy{} + err := r.Client().Get(ctx, rlpKey, rlp) + logger.V(1).Info("get rlp", "ratelimitpolicy", rlpKey, "err", err) if err != nil { return nil, err } - // gateways limits merged with the route level limits - mergedLimits := mergeLimits(httpRouteRLP, gwRLP) - // routeLimits referenced by multiple hostnames - for _, hostname := range httpRoute.Spec.Hostnames { - limits[string(hostname)] = append(limits[string(hostname)], mergedLimits...) - } - } - - return limits, nil -} - -// merged currently implemented with list append operation -func mergeLimits(routeRLP *kuadrantv1beta1.RateLimitPolicy, gwRLP *kuadrantv1beta1.RateLimitPolicy) []kuadrantv1beta1.Limit { - limits := routeRLP.FlattenLimits() - - if gwRLP == nil { - return limits + rateLimitIndex.Set(rlpKey, rlptools.LimitadorRateLimitsFromRLP(rlp)) } - // add gateway level limits - return append(limits, gwRLP.FlattenLimits()...) + return rateLimitIndex, nil } diff --git a/controllers/ratelimitpolicy_status.go b/controllers/ratelimitpolicy_status.go index b6e824750..f57717c76 100644 --- a/controllers/ratelimitpolicy_status.go +++ b/controllers/ratelimitpolicy_status.go @@ -9,11 +9,9 @@ import ( meta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" - gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "github.com/kuadrant/kuadrant-operator/pkg/common" ) @@ -21,12 +19,9 @@ const ( RLPAvailableConditionType string = "Available" ) -func (r *RateLimitPolicyReconciler) reconcileStatus(ctx context.Context, rlp *kuadrantv1beta1.RateLimitPolicy, targetNetworkObject client.Object, specErr error) (ctrl.Result, error) { +func (r *RateLimitPolicyReconciler) reconcileStatus(ctx context.Context, rlp *kuadrantv1beta2.RateLimitPolicy, specErr error) (ctrl.Result, error) { logger, _ := logr.FromContext(ctx) - newStatus, err := r.calculateStatus(ctx, rlp, targetNetworkObject, specErr) - if err != nil { - return reconcile.Result{}, err - } + newStatus := r.calculateStatus(ctx, rlp, specErr) equalStatus := rlp.Status.Equals(newStatus, logger) logger.V(1).Info("Status", "status is different", !equalStatus) @@ -60,27 +55,18 @@ func (r *RateLimitPolicyReconciler) reconcileStatus(ctx context.Context, rlp *ku return ctrl.Result{}, nil } -func (r *RateLimitPolicyReconciler) calculateStatus(ctx context.Context, rlp *kuadrantv1beta1.RateLimitPolicy, targetNetworkObject client.Object, specErr error) (*kuadrantv1beta1.RateLimitPolicyStatus, error) { - newStatus := &kuadrantv1beta1.RateLimitPolicyStatus{ +func (r *RateLimitPolicyReconciler) calculateStatus(_ context.Context, rlp *kuadrantv1beta2.RateLimitPolicy, specErr error) *kuadrantv1beta2.RateLimitPolicyStatus { + newStatus := &kuadrantv1beta2.RateLimitPolicyStatus{ // Copy initial conditions. Otherwise, status will always be updated Conditions: common.CopyConditions(rlp.Status.Conditions), ObservedGeneration: rlp.Status.ObservedGeneration, } - // Only makes sense for rlp's targeting a route - if common.IsTargetRefHTTPRoute(rlp.Spec.TargetRef) { - gwRateLimits, err := r.gatewaysRateLimits(ctx, targetNetworkObject) - if err != nil { - return nil, err - } - newStatus.GatewaysRateLimits = gwRateLimits - } - availableCond := r.availableCondition(specErr) meta.SetStatusCondition(&newStatus.Conditions, *availableCond) - return newStatus, nil + return newStatus } func (r *RateLimitPolicyReconciler) availableCondition(specErr error) *metav1.Condition { @@ -99,50 +85,3 @@ func (r *RateLimitPolicyReconciler) availableCondition(specErr error) *metav1.Co return cond } - -// gatewaysRateLimits returns all gateway-level rate limit configurations from all the gateways targeted by the ratelimitpolicy (directly or indirectly) -func (r *RateLimitPolicyReconciler) gatewaysRateLimits(ctx context.Context, targetNetworkObject client.Object) ([]kuadrantv1beta1.GatewayRateLimits, error) { - logger, _ := logr.FromContext(ctx) - - result := make([]kuadrantv1beta1.GatewayRateLimits, 0) - - for _, gwKey := range r.TargetedGatewayKeys(ctx, targetNetworkObject) { - gw := &gatewayapiv1beta1.Gateway{} - err := r.Client().Get(ctx, gwKey, gw) - logger.V(1).Info("get gateway", "key", gwKey, "err", err) - if err != nil { - if apierrors.IsNotFound(err) { - continue - } - return nil, err - } - - if gw.GetAnnotations() == nil { - continue - } - - if rlpKeyStr, ok := gw.GetAnnotations()[common.RateLimitPolicyBackRefAnnotation]; ok { - rlpKey, err := common.UnMarshallObjectKey(rlpKeyStr) - if err != nil { - logger.V(1).Info("gatewaysRateLimits", "cannot parse rlp back ref key", rlpKey, "err", err) - continue - } - gwRLP := &kuadrantv1beta1.RateLimitPolicy{} - err = r.Client().Get(ctx, rlpKey, gwRLP) - logger.V(1).Info("gatewaysRateLimits", "get gateway rlp", rlpKey, "err", err) - if err != nil { - if apierrors.IsNotFound(err) { - continue - } - return nil, err - } - - result = append(result, kuadrantv1beta1.GatewayRateLimits{ - GatewayName: gwKey.String(), - RateLimits: gwRLP.Spec.RateLimits, - }) - } - } - - return result, nil -} diff --git a/controllers/ratelimitpolicy_wasm_plugins.go b/controllers/ratelimitpolicy_wasm_plugins.go index cb400506c..78cb994f3 100644 --- a/controllers/ratelimitpolicy_wasm_plugins.go +++ b/controllers/ratelimitpolicy_wasm_plugins.go @@ -11,13 +11,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "github.com/kuadrant/kuadrant-operator/pkg/common" "github.com/kuadrant/kuadrant-operator/pkg/reconcilers" "github.com/kuadrant/kuadrant-operator/pkg/rlptools" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" ) -func (r *RateLimitPolicyReconciler) reconcileWASMPluginConf(ctx context.Context, rlp *kuadrantv1beta1.RateLimitPolicy, gwDiffObj *reconcilers.GatewayDiff) error { +func (r *RateLimitPolicyReconciler) reconcileWASMPluginConf(ctx context.Context, rlp *kuadrantv1beta2.RateLimitPolicy, gwDiffObj *reconcilers.GatewayDiff) error { logger, _ := logr.FromContext(ctx) for _, gw := range gwDiffObj.GatewaysWithInvalidPolicyRef { @@ -119,86 +120,117 @@ func (r *RateLimitPolicyReconciler) gatewayWASMPlugin(ctx context.Context, gw co } // returns nil when there is no rate limit policy to apply -func (r *RateLimitPolicyReconciler) wasmPluginConfig(ctx context.Context, - gw common.GatewayWrapper, rlpRefs []client.ObjectKey) (*rlptools.WASMPlugin, error) { +func (r *RateLimitPolicyReconciler) wasmPluginConfig(ctx context.Context, gw common.GatewayWrapper, rlpRefs []client.ObjectKey) (*wasm.Plugin, error) { logger, _ := logr.FromContext(ctx) + logger = logger.WithName("wasmPluginConfig").WithValues("gateway", gw.Key()) - routeRLPList := make([]*kuadrantv1beta1.RateLimitPolicy, 0) - var gwRLP *kuadrantv1beta1.RateLimitPolicy + type store struct { + rlp kuadrantv1beta2.RateLimitPolicy + route gatewayapiv1beta1.HTTPRoute + skip bool + } + rlps := make(map[string]*store, len(rlpRefs)) + routeKeys := make(map[string]struct{}, 0) + var gwRLPKey string + + // store all rlps and find the one that targets the gateway (if there is one) for _, rlpKey := range rlpRefs { - rlp := &kuadrantv1beta1.RateLimitPolicy{} + rlp := &kuadrantv1beta2.RateLimitPolicy{} err := r.Client().Get(ctx, rlpKey, rlp) - logger.V(1).Info("wasmPluginConfig", "get rlp", rlpKey, "err", err) + logger.V(1).Info("get rlp", "ratelimitpolicy", rlpKey, "err", err) if err != nil { return nil, err } + // target ref is a HTTPRoute if common.IsTargetRefHTTPRoute(rlp.Spec.TargetRef) { - routeRLPList = append(routeRLPList, rlp) - } else if common.IsTargetRefGateway(rlp.Spec.TargetRef) { - if gwRLP == nil { - gwRLP = rlp - } else { - return nil, fmt.Errorf("wasmPluginConfig: multiple gateway RLP found and only one expected. rlp keys: %v", rlpRefs) + route, err := r.FetchValidHTTPRoute(ctx, rlp.TargetKey()) + if err != nil { + return nil, err } + rlps[rlpKey.String()] = &store{rlp: *rlp, route: *route} + routeKeys[client.ObjectKeyFromObject(route).String()] = struct{}{} + continue } + + // target ref is a Gateway + if rlps[rlpKey.String()] != nil { + return nil, fmt.Errorf("wasmPluginConfig: multiple gateway RLP found and only one expected. rlp keys: %v", rlpRefs) + } + gwRLPKey = rlpKey.String() + rlps[gwRLPKey] = &store{rlp: *rlp} } - gatewayActions := rlptools.GatewayActionsByDomain{} + gwHostnames := gw.Hostnames() + if len(gwHostnames) == 0 { + gwHostnames = []gatewayapiv1beta1.Hostname{"*"} + } - if gwRLP != nil { - if len(gw.Hostnames()) == 0 { - // wildcard domain - gatewayActions["*"] = append(gatewayActions["*"], rlptools.GatewayActionsFromRateLimitPolicy(gwRLP, nil)...) + // if there is a gateway rlp, fake a single httproute with all rules from all httproutes accepted by the gateway, + // that do not have a rlp of its own, so we can generate wasm rules for those cases + if gwRLPKey != "" { + rules := make([]gatewayapiv1beta1.HTTPRouteRule, 0) + routes := r.FetchAcceptedGatewayHTTPRoutes(ctx, rlps[gwRLPKey].rlp.TargetKey()) + for idx := range routes { + route := routes[idx] + // skip routes that have a rlp of its own + if _, found := routeKeys[client.ObjectKeyFromObject(&route).String()]; found { + continue + } + rules = append(rules, route.Spec.Rules...) + } + if len(rules) == 0 { + logger.V(1).Info("no httproutes attached to the targeted gateway, skipping wasm config for the gateway rlp", "ratelimitpolicy", gwRLPKey) + rlps[gwRLPKey].skip = true } else { - for _, gwHostname := range gw.Hostnames() { - gatewayActions[gwHostname] = append(gatewayActions[gwHostname], rlptools.GatewayActionsFromRateLimitPolicy(gwRLP, nil)...) + rlps[gwRLPKey].route = gatewayapiv1beta1.HTTPRoute{ + Spec: gatewayapiv1beta1.HTTPRouteSpec{ + Hostnames: gwHostnames, + Rules: rules, + }, } } } - for _, httpRouteRLP := range routeRLPList { - httpRoute, err := r.FetchValidHTTPRoute(ctx, httpRouteRLP.TargetKey()) - if err != nil { - return nil, err + wasmPlugin := &wasm.Plugin{ + FailureMode: wasm.FailureModeDeny, + RateLimitPolicies: make([]wasm.RateLimitPolicy, 0), + } + + for _, rlpKey := range rlpRefs { + s := rlps[rlpKey.String()] + if s.skip { + continue + } + rlp := s.rlp + route := s.route + + // narrow the list of hostnames specified in the route so we don't generate wasm rules that only apply to other gateways + // this is a no-op for the gateway rlp + hostnames := common.FilterValidSubdomains(gwHostnames, route.Spec.Hostnames) + if len(hostnames) == 0 { // it should only happen when the route specifies no hostnames + hostnames = gwHostnames } + route.Spec.Hostnames = hostnames - // gateways limits merged with the route level limits - mergedGatewayActions := mergeGatewayActions(httpRouteRLP, gwRLP, httpRoute) - // routeLimits referenced by multiple hostnames - for _, hostname := range httpRoute.Spec.Hostnames { - gatewayActions[string(hostname)] = append(gatewayActions[string(hostname)], mergedGatewayActions...) + rules := rlptools.WasmRules(&rlp, &route) + if len(rules) == 0 { + continue // no need to add the policy if there are no rules; a rlp can return no rules if all its limits fail to match any route rule } - } - wasmPlugin := &rlptools.WASMPlugin{ - FailureModeDeny: true, - RateLimitPolicies: make([]rlptools.RateLimitPolicy, 0), + wasmPlugin.RateLimitPolicies = append(wasmPlugin.RateLimitPolicies, wasm.RateLimitPolicy{ + Name: rlpKey.String(), + Domain: rlptools.LimitsNamespaceFromRLP(&rlp), + Rules: rules, + Hostnames: common.HostnamesToStrings(hostnames), // we might be listing more hostnames than needed due to route selectors hostnames possibly being more restrictive + Service: common.KuadrantRateLimitClusterName, + }) } - // One RateLimitPolicy per domain - for domain, gatewayActionList := range gatewayActions { - rateLimitPolicy := rlptools.RateLimitPolicy{ - Name: domain, - RateLimitDomain: common.MarshallNamespace(gw.Key(), domain), - UpstreamCluster: common.KuadrantRateLimitClusterName, - Hostnames: []string{domain}, - GatewayActions: gatewayActionList, - } - wasmPlugin.RateLimitPolicies = append(wasmPlugin.RateLimitPolicies, rateLimitPolicy) + // avoid building a wasm plugin config if there are no rules to apply + if len(wasmPlugin.RateLimitPolicies) == 0 { + return nil, nil } return wasmPlugin, nil } - -// merge operations currently implemented with list append operation -func mergeGatewayActions(routeRLP *kuadrantv1beta1.RateLimitPolicy, gwRLP *kuadrantv1beta1.RateLimitPolicy, route *gatewayapiv1beta1.HTTPRoute) []rlptools.GatewayAction { - gatewayActions := rlptools.GatewayActionsFromRateLimitPolicy(routeRLP, route) - - if gwRLP == nil { - return gatewayActions - } - - // add gateway level actions - return append(gatewayActions, rlptools.GatewayActionsFromRateLimitPolicy(gwRLP, nil)...) -} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index c19d467c2..5ed557e80 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -44,6 +44,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "github.com/kuadrant/kuadrant-operator/pkg/reconcilers" //+kubebuilder:scaffold:imports ) @@ -79,6 +80,9 @@ var _ = BeforeSuite(func() { err = kuadrantv1beta1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = kuadrantv1beta2.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = gatewayapiv1beta1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) diff --git a/go.mod b/go.mod index 9405b3d00..4e8ed2a00 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/kuadrant/kuadrant-operator go 1.19 require ( + github.com/elliotchance/orderedmap/v2 v2.2.0 github.com/go-logr/logr v1.2.4 github.com/golang/protobuf v1.5.3 github.com/google/go-cmp v0.5.9 @@ -10,15 +11,15 @@ require ( github.com/kuadrant/authorino v0.14.0 github.com/kuadrant/authorino-operator v0.8.0 github.com/kuadrant/limitador-operator v0.4.0 - github.com/onsi/ginkgo/v2 v2.6.0 - github.com/onsi/gomega v1.24.1 + github.com/onsi/ginkgo/v2 v2.7.0 + github.com/onsi/gomega v1.24.2 go.uber.org/zap v1.24.0 golang.org/x/sync v0.1.0 google.golang.org/protobuf v1.31.0 gotest.tools v2.2.0+incompatible - istio.io/api v0.0.0-20220907134937-b47284812feb - istio.io/client-go v1.15.1-0.20220907135338-7f6428013c07 - istio.io/istio v0.0.0-20220923214536-bf836f0be536 + istio.io/api v0.0.0-20230712174848-a2b2de508c88 + istio.io/client-go v1.17.4-0.20230712175648-f1263a806483 + istio.io/istio v0.0.0-20230719200611-681b4f65a752 k8s.io/api v0.26.1 k8s.io/apiextensions-apiserver v0.26.1 k8s.io/apimachinery v0.26.1 @@ -29,118 +30,47 @@ require ( ) require ( - cloud.google.com/go v0.107.0 // indirect + cloud.google.com/go v0.108.0 // indirect cloud.google.com/go/compute v1.15.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/logging v1.6.1 // indirect - cloud.google.com/go/longrunning v0.3.0 // indirect - github.com/OneOfOne/xxhash v1.2.8 // indirect - github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect - github.com/agnivade/levenshtein v1.1.1 // indirect - github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect - github.com/authzed/authzed-go v0.7.0 // indirect - github.com/authzed/grpcutil v0.0.0-20230109193425-40ce0530e048 // indirect + cloud.google.com/go/longrunning v0.4.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect - github.com/cenkalti/backoff/v4 v4.2.0 // indirect - github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect - github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect - github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b // indirect - github.com/coocood/freecache v1.1.1 // indirect - github.com/coreos/go-oidc v2.2.1+incompatible // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/eko/gocache v1.2.0 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/envoyproxy/go-control-plane v0.10.3 // indirect - github.com/envoyproxy/protoc-gen-validate v0.9.1 // indirect + github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.2.3 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect - github.com/go-openapi/swag v0.21.1 // indirect - github.com/go-redis/redis/v8 v8.11.5 // indirect - github.com/gobwas/glob v0.2.3 // indirect - github.com/goccy/go-json v0.9.7 // indirect - github.com/gogo/googleapis v1.4.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.1 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/google/cel-go v0.12.6 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/gnostic v0.6.9 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/jzelinskie/stringz v0.0.0-20210414224931-d6a8ce844a70 // indirect - github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect - github.com/lestrrat-go/blackmagic v1.0.0 // indirect - github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/iter v1.0.1 // indirect - github.com/lestrrat-go/jwx v1.2.25 // indirect - github.com/lestrrat-go/option v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/natefinch/lumberjack v2.0.0+incompatible // indirect - github.com/nxadm/tail v1.4.8 // indirect - github.com/open-policy-agent/opa v0.52.0 // indirect - github.com/pegasus-kv/thrift v0.13.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect github.com/prometheus/client_golang v1.15.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect - github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect - github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stoewer/go-strcase v1.2.0 // indirect - github.com/stretchr/testify v1.8.2 // indirect - github.com/tchap/go-patricia/v2 v2.3.1 // indirect - github.com/tidwall/gjson v1.14.0 // indirect - github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/yashtewari/glob-intersection v0.1.0 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 // indirect - go.opentelemetry.io/otel v1.14.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0 // indirect - go.opentelemetry.io/otel/metric v0.37.0 // indirect - go.opentelemetry.io/otel/sdk v1.14.0 // indirect - go.opentelemetry.io/otel/trace v1.14.0 // indirect - go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.8.0 // indirect - golang.org/x/crypto v0.1.0 // indirect + go.uber.org/multierr v1.9.0 // indirect golang.org/x/net v0.9.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/sys v0.7.0 // indirect @@ -148,22 +78,19 @@ require ( golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect - google.golang.org/api v0.103.0 // indirect + google.golang.org/api v0.107.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect google.golang.org/grpc v1.54.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect - gopkg.in/square/go-jose.v2 v2.6.0 // indirect - gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - istio.io/pkg v0.0.0-20220907025138-198870de7239 // indirect + istio.io/pkg v0.0.0-20230523222708-7056be172a30 // indirect k8s.io/component-base v0.26.1 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 // indirect k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 4285abe27..8a6a43fa3 100644 --- a/go.sum +++ b/go.sum @@ -1,348 +1,100 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1 h1:vpK6iQWv/2uUeFJth4/cBHsQAGjn1iIE6AAlxipRaA0= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= -cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.10.0 h1:aoLIYaA1fX3ywihqpBk2APQKOo20nXsp1GEZQbx5Jk4= -cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go v0.108.0 h1:xntQwnfn8oHGX0crLVinvHM+AhXvi3QHQIEcX/2hiWk= +cloud.google.com/go v0.108.0/go.mod h1:lNUfQqusBJp0bgAg6qrHgYFYbTB+dOiob1itwnlD33Q= cloud.google.com/go/compute v1.15.1 h1:7UGq3QknM33pw5xATlpzeoomNxsacIVvTqTTvbfajmE= cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/logging v1.4.2 h1:Mu2Q75VBDQlW1HlBMjTX4X84UFR73G1TiLlRYc/b7tA= -cloud.google.com/go/logging v1.4.2/go.mod h1:jco9QZSx8HiVVqLJReq7z7bVdj0P1Jb9PDFs63T+axo= +cloud.google.com/go/iam v0.10.0 h1:fpP/gByFs6US1ma53v7VxhvbJpO2Aapng6wabJ99MuI= cloud.google.com/go/logging v1.6.1 h1:ZBsZK+JG+oCDT+vaxwqF2egKNRjz8soXiS6Xv79benI= cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= -cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= -cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +cloud.google.com/go/longrunning v0.4.0 h1:v+X4EwhHl6xE+TG1XgXj4T1XpKKs7ZevcAJ3FOu0YmY= +cloud.google.com/go/longrunning v0.4.0/go.mod h1:eF3Qsw58iX/bkKtVjMTYpH0LRjQ2goDkjkNQTlzq/ZM= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= -github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10= -github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= -github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/allegro/bigcache/v2 v2.2.5/go.mod h1:FppZsIO+IZk7gCuj5FiIDHGygD9xvWQcqg1uIPMb6tY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= -github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/authzed/authzed-go v0.7.0 h1:etnzHUAIyxGiEaFYJPYkHTHzxCYWEGzZQMgVLe4xRME= -github.com/authzed/authzed-go v0.7.0/go.mod h1:bmjzzIQ34M0+z8NO9SLjf4oA0A9Ka9gUWVzeSbD0E7c= -github.com/authzed/grpcutil v0.0.0-20230109193425-40ce0530e048 h1:pBStde+5xTAEFP5gGkOMbnDbpCHg1hAWBv7N0VEnDMY= -github.com/authzed/grpcutil v0.0.0-20230109193425-40ce0530e048/go.mod h1:rqjY3zyK/YP7NID9+B2BdIRRkvnK+cdf9/qya/zaFZE= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= -github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= -github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= -github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20220520190051-1e77728a1eaa h1:B/lvg4tQ5hfFZd4V2hcSfFVfUvAK6GSFKxIIzwnkv8g= -github.com/cncf/xds/go v0.0.0-20220520190051-1e77728a1eaa/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b h1:ACGZRIr7HsgBKHsueQ1yM4WaVaXh21ynwqsF8M8tXhA= -github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/coocood/freecache v1.1.1 h1:uukNF7QKCZEdZ9gAV7WQzvh0SbjwdMF6m3x3rxEkaPc= -github.com/coocood/freecache v1.1.1/go.mod h1:OKrEjkGVoxZhyWAJoeFi5BMLUJm2Tit0kpGkIr7NGYY= -github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= -github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= -github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/eko/gocache v1.2.0 h1:SCtTs65qMXjhdtu62yHPCQuzdMkQjP+fQmkNrVutkRw= -github.com/eko/gocache v1.2.0/go.mod h1:6u8/2bnr+nOf87mRXWS710rqNNZUECF4CGsPNnsoJ78= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk= +github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q= +github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= +github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/go-control-plane v0.10.3-0.20220719090109-b024c36d9935 h1:1P6HktLf+VNpEwASft2E0KU7ddeuu73UMnFpawKuD58= -github.com/envoyproxy/go-control-plane v0.10.3-0.20220719090109-b024c36d9935/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= -github.com/envoyproxy/go-control-plane v0.10.3 h1:xdCVXxEe0Y3FQith+0cj2irwZudqGYvecuLB1HtdexY= -github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230202164348-98e9e8eacc1a h1:EWOGEDoAqZrA4mM2G8g74h90oBV9wunqguCSMVSGYJg= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.7 h1:qcZcULcd/abmQg6dwigimCNEyi4gg31M/xaciQlDml8= -github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1 h1:PS7VIOgmSVhWUEeZwTe7z7zouA22Cr590PzXKbZHOVY= -github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= -github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-redis/redis/v8 v8.8.2/go.mod h1:F7resOH5Kdug49Otu24RjHWwgK7u9AmtqWMnCV1iP5Y= -github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= -github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI= -github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -353,1033 +105,261 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M= -github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/cel-go v0.13.0 h1:z+8OBOcmh7IeKyqwT/6IlnMvy621fYUqnTVPEdegGlU= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0 h1:zO8WHNx/MYiAKJ3d5spxZXZE6KHmIQGQcAzwUzV7qQw= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 h1:lLT7ZLSzGLI08vc9cpd+tYmNWjdKDqyr/2L+f6U12Fk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/jzelinskie/stringz v0.0.0-20210414224931-d6a8ce844a70 h1:thTca5Eyouk5CEcJ75Cbw9CSAGE7TAc6rIi+WgHWpOE= -github.com/jzelinskie/stringz v0.0.0-20210414224931-d6a8ce844a70/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kuadrant/authorino v0.10.0 h1:BX3sElCbN+Yg01iACRE8CbMBNdBabYP0zENsu/+MwCA= -github.com/kuadrant/authorino v0.10.0/go.mod h1:5aDJyNo1H0++8xcsh11gZCWHh2+fZu1iQrSTtgphbi4= github.com/kuadrant/authorino v0.14.0 h1:RddfLONog411sQl18U56LVHp1Ab/m+vFyqHa06CZ3pQ= github.com/kuadrant/authorino v0.14.0/go.mod h1:LVMrMZMNTOrjdReSI5Br6kLrhy3oVFMq3W0BiyHqVG4= -github.com/kuadrant/authorino-operator v0.4.1 h1:dumqrOQzLTJNoAq/MlBYquhK6klvsWBM8XovBss4vNk= -github.com/kuadrant/authorino-operator v0.4.1/go.mod h1:oC3pRooAkzsiR66eHQY/vZ2CziORTrl8Yx1yQX0AsT0= github.com/kuadrant/authorino-operator v0.8.0 h1:wCiZhIcVxqS/cfl0QPz4f7uCpDdqywFKht1pW6FaJew= github.com/kuadrant/authorino-operator v0.8.0/go.mod h1:cYayOkqeQAlRPulL9lo61KPJxSaTc+7oZ9qMehv7frk= github.com/kuadrant/limitador-operator v0.4.0 h1:HgJi7LuOsenCUMs2ACCfKMKsKpfHcqmmwVmqpci0hw4= github.com/kuadrant/limitador-operator v0.4.0/go.mod h1:5fQo2XwxPr7bDObut9sK5sHCnK4hwAmTsTptaYvGfuc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= -github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= -github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= -github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= -github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A= -github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0otA= -github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY= -github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= -github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= -github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= -github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= -github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= -github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= -github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/open-policy-agent/opa v0.52.0 h1:Rv3F+VCDqsufaiYy/3S9/Iuk0yfcREK4iZmWbNsKZjA= -github.com/open-policy-agent/opa v0.52.0/go.mod h1:2n99s7WY/BXZUWUOq10JdTgK+G6XM4FYGoe7kQ5Vg0s= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4= -github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow= +github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= +github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac h1:jWKYCNlX4J5s8M0nHYkh7Y7c9gRVDEb3mq51j5J0F5M= -github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= -github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stoewer/go-strcase v1.2.1 h1:/1JWd+AcWPzkcGLEmjUCka99YqGOtTnp1H/wcP+uap4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= -github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= -github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w= -github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg= -github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0 h1:5jD3teb4Qh7mx/nfzq4jO2WFFpvXD0vYWFDrdvNWmXk= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0/go.mod h1:UMklln0+MRhZC4e3PwmN3pCtq4DyIadWw4yikh6bNrw= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 h1:lE9EJyw3/JhrjWH/hEy9FptnalDQgj7vpbgC2KCCCxE= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0/go.mod h1:pcQ3MM3SWvrA71U4GDqv9UFDJ3HQsW7y5ZO3tDTlUdI= -go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg= -go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= -go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 h1:/fXHZHGvro6MVqV34fJzDhi7sHGpX3Ej/Qjmfn003ho= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0/go.mod h1:UFG7EBMRdXyFstOwH028U0sVf+AvukSGhF0g8+dmNG8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 h1:TKf2uAs2ueguzLaxOCBXNpHxfO/aC7PAdDsSH0IbeRQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0/go.mod h1:HrbCVv40OOLTABmOn1ZWty6CHXkU8DK/Urc43tHug70= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 h1:ap+y8RXX3Mu9apKVtOkM6WSFESLM8K3wNQyOU8sWHcc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0 h1:3jAYbRHQAqzLjd9I4tzxwJ8Pk/N6AqBcF6m1ZHrxG94= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0/go.mod h1:+N7zNjIJv4K+DeX67XXET0P+eIciESgaFDBqh+ZJFS4= -go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc= -go.opentelemetry.io/otel/metric v0.37.0 h1:pHDQuLQOZwYD+Km0eb657A25NaRzy0a+eLyKfDXedEs= -go.opentelemetry.io/otel/metric v0.37.0/go.mod h1:DmdaHfGt54iV6UKxsV9slj2bBRJcKC1B1uvDLIioc1s= -go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA= -go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= -go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM= -go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg= -go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= -go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.starlark.net v0.0.0-20211013185944-b0039bd2cfe3 h1:oBcONsksxvpeodDrLjiMDaKHXKAVVfAydhe/792CE/o= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a h1:tlXy25amD5A7gOfbXdqCGN5k8ESEed/Ee1E5RcrYnqU= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 h1:Frnccbp+ok2GkUS2tC84yAq/U9Vg+0sIO7aRL3T4Xnc= -golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.96.0 h1:F60cuQPJq7K7FzsxMYHAUJSiXh2oKctHxBMbDygxhfM= -google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ= -google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.107.0 h1:I2SlFjD8ZWabaIFOfeEDg3pf0BHJDh6iYQ1ic3Yu/UU= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 h1:mmbq5q8M1t7dhkLw320YK4PsOXm6jdnUAkErImaIqOg= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1388,45 +368,23 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs= -gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -1438,57 +396,45 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +helm.sh/helm/v3 v3.11.1 h1:cmL9fFohOoNQf+wnp2Wa0OhNFH0KFnSzEkVxi3fcc3I= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -istio.io/api v0.0.0-20220907134937-b47284812feb h1:gww8VW5FEh7e1qvdtJfLiNL7tpPzeSCZkXtwUM1gCCc= -istio.io/api v0.0.0-20220907134937-b47284812feb/go.mod h1:hQkF0Q19MCmfOTre/Sg4KvrwwETq45oaFplnBm2p4j8= -istio.io/client-go v1.15.1-0.20220907135338-7f6428013c07 h1:0NaIOpISKJH+sAVsdZzJA0UfTqT6kiJHg4pnKrmP3A0= -istio.io/client-go v1.15.1-0.20220907135338-7f6428013c07/go.mod h1:y7N6Hi3uohxutNPWhilQ0sTVzuhWWA6JyRHNj3QyMxU= -istio.io/istio v0.0.0-20220923214536-bf836f0be536 h1:fZZTZUPpzAJqGqKzijw7j5ss1kAZXYHO8YNfdhZmn48= -istio.io/istio v0.0.0-20220923214536-bf836f0be536/go.mod h1:TLnweNm1q+jQT0mkQtKbJkz8GLEQm2d31oisK9vfENA= -istio.io/pkg v0.0.0-20220907025138-198870de7239 h1:fT4mErRurULEFGqBYwrY/a9lq3BC6qgilzsUNFnnw6w= -istio.io/pkg v0.0.0-20220907025138-198870de7239/go.mod h1:kcBYN5TiyGFM2bs4b7K81j+YeDZ4JrINP+brV9ehZe0= +istio.io/api v0.0.0-20230712174848-a2b2de508c88 h1:w7lSk+XcYNzGC5xtHBFvaV7QTBMQC5nM4l8xxFslGgk= +istio.io/api v0.0.0-20230712174848-a2b2de508c88/go.mod h1:owGDRg9uqMob8CN1gxaOzk6nJxnbT8wrP7PmggpJHHY= +istio.io/client-go v1.17.4-0.20230712175648-f1263a806483 h1:725Oo7K4eqHwCifG+LoOzsJS/4n+9bHPOoI0xj1+/q0= +istio.io/client-go v1.17.4-0.20230712175648-f1263a806483/go.mod h1:994/wFLmyN7Th6Cat2pACSwvp6VaGYlonm3mpuRAOq8= +istio.io/istio v0.0.0-20230719200611-681b4f65a752 h1:BRz0SZiDW6DIMVXFtRXsdKiw2CsuuzlKNj3RURIaX7g= +istio.io/istio v0.0.0-20230719200611-681b4f65a752/go.mod h1:vNy6YexSYTc1NC/sS49RvRarxqclCjI+EbabodxNHNk= +istio.io/pkg v0.0.0-20230523222708-7056be172a30 h1:7sh9eDgAlYBWaea62dkO47ORk6PZsmU/bvUGY0q09ws= +istio.io/pkg v0.0.0-20230523222708-7056be172a30/go.mod h1:BrWkMDfL8n60pWeRnHoNBckpgBOzMFasZsGUGCP9grE= k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= -k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/cli-runtime v0.26.0 h1:aQHa1SyUhpqxAw1fY21x2z2OS5RLtMJOCj7tN4oq8mw= k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596 h1:8cNCQs+WqqnSpZ7y0LMQPKD+RZUHU17VqLPMW3qxnxc= +k8s.io/kube-openapi v0.0.0-20230109183929-3758b55a6596/go.mod h1:/BYxry62FuDzmI+i9B+X2pqfySRmSOW2ARmj5Zbqhj0= +k8s.io/kubectl v0.26.0 h1:xmrzoKR9CyNdzxBmXV7jW9Ln8WMrwRK6hGbbf69o4T0= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/gateway-api v0.6.2 h1:583XHiX2M2bKEA0SAdkoxL1nY73W1+/M+IAm8LJvbEA= sigs.k8s.io/gateway-api v0.6.2/go.mod h1:EYJT+jlPWTeNskjV0JTki/03WX1cyAnBhwBJfYHpV/0= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= +sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= +sigs.k8s.io/mcs-api v0.1.0 h1:edDbg0oRGfXw8TmZjKYep06LcJLv/qcYLidejnUp0PM= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/main.go b/main.go index c8963393e..877773e7f 100644 --- a/main.go +++ b/main.go @@ -46,6 +46,7 @@ import ( gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "github.com/kuadrant/kuadrant-operator/controllers" "github.com/kuadrant/kuadrant-operator/pkg/common" "github.com/kuadrant/kuadrant-operator/pkg/log" @@ -73,6 +74,7 @@ func init() { utilruntime.Must(istioapis.AddToScheme(scheme)) utilruntime.Must(maistraapis.AddToScheme(scheme)) utilruntime.Must(kuadrantv1beta1.AddToScheme(scheme)) + utilruntime.Must(kuadrantv1beta2.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme diff --git a/pkg/common/common.go b/pkg/common/common.go index 7c0e24bd7..b6f999d63 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -47,6 +47,10 @@ type KuadrantPolicy interface { GetRulesHostnames() []string } +func Ptr[T any](t T) *T { + return &t +} + // FetchEnv fetches the value of the environment variable with the specified key, // or returns the default value if the variable is not found or has an empty value. // If an error occurs during the lookup, the function returns the default value. @@ -87,7 +91,7 @@ func NamespacedNameToObjectKey(namespacedName, defaultNamespace string) client.O // Contains checks if the given target string is present in the slice of strings 'slice'. // It returns true if the target string is found in the slice, false otherwise. -func Contains(slice []string, target string) bool { +func Contains[T comparable](slice []T, target T) bool { for idx := range slice { if slice[idx] == target { return true @@ -96,6 +100,44 @@ func Contains(slice []string, target string) bool { return false } +// SameElements checks if the two slices contain the exact same elements. Order does not matter. +func SameElements[T comparable](s1, s2 []T) bool { + if len(s1) != len(s2) { + return false + } + for _, v := range s1 { + if !Contains(s2, v) { + return false + } + } + return true +} + +func Intersect[T comparable](slice1, slice2 []T) bool { + for _, item := range slice1 { + if Contains(slice2, item) { + return true + } + } + return false +} + +func Intersection[T comparable](slice1, slice2 []T) []T { + smallerSlice := slice1 + largerSlice := slice2 + if len(slice1) > len(slice2) { + smallerSlice = slice2 + largerSlice = slice1 + } + var result []T + for _, item := range smallerSlice { + if Contains(largerSlice, item) { + result = append(result, item) + } + } + return result +} + func Find[T any](slice []T, match func(T) bool) (*T, bool) { for _, item := range slice { if match(item) { @@ -114,6 +156,17 @@ func Map[T, U any](slice []T, f func(T) U) []U { return arr } +// Filter filters the input slice using the given predicate function and returns a new slice with the results. +func Filter[T any](slice []T, f func(T) bool) []T { + arr := make([]T, 0) + for _, e := range slice { + if f(e) { + arr = append(arr, e) + } + } + return arr +} + // SliceCopy copies the elements from the input slice into the output slice, and returns the output slice. func SliceCopy[T any](s1 []T) []T { s2 := make([]T, len(s1)) @@ -172,11 +225,6 @@ func UnMarshallLimitNamespace(ns string) (client.ObjectKey, string, error) { return objKey, domain, nil } -// MarshallNamespace serializes limit namespace with format "gwNS/gwName#domain" -func MarshallNamespace(gwKey client.ObjectKey, domain string) string { - return fmt.Sprintf("%s/%s#%s", gwKey.Namespace, gwKey.Name, domain) -} - // UnMarshallObjectKey takes a string input and converts it into an ObjectKey struct that // can be used to access a specific Kubernetes object. The input string is expected to be in the format "namespace/name". // If the input string does not contain a NamespaceSeparator (typically '/') @@ -192,11 +240,9 @@ func UnMarshallObjectKey(keyStr string) (client.ObjectKey, error) { // HostnamesToStrings converts []gatewayapi_v1alpha2.Hostname to []string func HostnamesToStrings(hostnames []gatewayapiv1beta1.Hostname) []string { - hosts := make([]string, len(hostnames)) - for i, h := range hostnames { - hosts[i] = string(h) - } - return hosts + return Map(hostnames, func(hostname gatewayapiv1beta1.Hostname) string { + return string(hostname) + }) } // ValidSubdomains returns (true, "") when every single subdomains item @@ -221,3 +267,16 @@ func ValidSubdomains(domains, subdomains []string) (bool, string) { } return true, "" } + +// FilterValidSubdomains returns every subdomain that is a subset of at least one of the (super) domains specified in the first argument. +func FilterValidSubdomains(domains, subdomains []gatewayapiv1beta1.Hostname) []gatewayapiv1beta1.Hostname { + arr := make([]gatewayapiv1beta1.Hostname, 0) + for _, subsubdomain := range subdomains { + if _, found := Find(domains, func(domain gatewayapiv1beta1.Hostname) bool { + return Name(subsubdomain).SubsetOf(Name(domain)) + }); found { + arr = append(arr, subsubdomain) + } + } + return arr +} diff --git a/pkg/common/common_test.go b/pkg/common/common_test.go index cb093479b..94d01fc69 100644 --- a/pkg/common/common_test.go +++ b/pkg/common/common_test.go @@ -250,6 +250,274 @@ func TestContains(t *testing.T) { } } +func TestContainsWithInts(t *testing.T) { + testCases := []struct { + name string + slice []int + target int + expected bool + }{ + { + name: "when slice has one target item then return true", + slice: []int{1}, + target: 1, + expected: true, + }, + { + name: "when slice is empty then return false", + slice: []int{}, + target: 2, + expected: false, + }, + { + name: "when target is in a slice then return true", + slice: []int{1, 2, 3}, + target: 2, + expected: true, + }, + { + name: "when no target in a slice then return false", + slice: []int{1, 2, 3}, + target: 4, + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if Contains(tc.slice, tc.target) != tc.expected { + t.Errorf("when slice=%v and target=%d, expected=%v, but got=%v", tc.slice, tc.target, tc.expected, !tc.expected) + } + }) + } +} + +func TestSameElements(t *testing.T) { + testCases := []struct { + name string + slice1 []string + slice2 []string + expected bool + }{ + { + name: "when slice1 and slice2 contain the same elements then return true", + slice1: []string{"test-gw1", "test-gw2", "test-gw3"}, + slice2: []string{"test-gw1", "test-gw2", "test-gw3"}, + expected: true, + }, + { + name: "when slice1 and slice2 contain unique elements then return false", + slice1: []string{"test-gw1", "test-gw2"}, + slice2: []string{"test-gw1", "test-gw3"}, + expected: false, + }, + { + name: "when both slices are empty then return true", + slice1: []string{}, + slice2: []string{}, + expected: true, + }, + { + name: "when both slices are nil then return true", + expected: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if SameElements(tc.slice1, tc.slice2) != tc.expected { + t.Errorf("when slice1=%v and slice2=%v, expected=%v, but got=%v", tc.slice1, tc.slice2, tc.expected, !tc.expected) + } + }) + } +} + +func TestIntersect(t *testing.T) { + testCases := []struct { + name string + slice1 []string + slice2 []string + expected bool + }{ + { + name: "when slice1 and slice2 have one common item then return true", + slice1: []string{"test-gw1", "test-gw2"}, + slice2: []string{"test-gw1", "test-gw3", "test-gw4"}, + expected: true, + }, + { + name: "when slice1 and slice2 have no common item then return false", + slice1: []string{"test-gw1", "test-gw2"}, + slice2: []string{"test-gw3", "test-gw4"}, + expected: false, + }, + { + name: "when slice1 is empty then return false", + slice1: []string{}, + slice2: []string{"test-gw3", "test-gw4"}, + expected: false, + }, + { + name: "when slice2 is empty then return false", + slice1: []string{"test-gw1", "test-gw2"}, + slice2: []string{}, + expected: false, + }, + { + name: "when both slices are empty then return false", + slice1: []string{}, + slice2: []string{}, + expected: false, + }, + { + name: "when slice1 is nil then return false", + slice2: []string{"test-gw3", "test-gw4"}, + expected: false, + }, + { + name: "when slice2 is nil then return false", + slice1: []string{"test-gw1", "test-gw2"}, + expected: false, + }, + { + name: "when both slices are nil then return false", + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if Intersect(tc.slice1, tc.slice2) != tc.expected { + t.Errorf("when slice1=%v and slice2=%v, expected=%v, but got=%v", tc.slice1, tc.slice2, tc.expected, !tc.expected) + } + }) + } +} + +func TestIntersectWithInts(t *testing.T) { + testCases := []struct { + name string + slice1 []int + slice2 []int + expected bool + }{ + { + name: "when slice1 and slice2 have one common item then return true", + slice1: []int{1, 2}, + slice2: []int{1, 3, 4}, + expected: true, + }, + { + name: "when slice1 and slice2 have no common item then return false", + slice1: []int{1, 2}, + slice2: []int{3, 4}, + expected: false, + }, + { + name: "when slice1 is empty then return false", + slice1: []int{}, + slice2: []int{3, 4}, + expected: false, + }, + { + name: "when slice2 is empty then return false", + slice1: []int{1, 2}, + slice2: []int{}, + expected: false, + }, + { + name: "when both slices are empty then return false", + slice1: []int{}, + slice2: []int{}, + expected: false, + }, + { + name: "when slice1 is nil then return false", + slice2: []int{3, 4}, + expected: false, + }, + { + name: "when slice2 is nil then return false", + slice1: []int{1, 2}, + expected: false, + }, + { + name: "when both slices are nil then return false", + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if Intersect(tc.slice1, tc.slice2) != tc.expected { + t.Errorf("when slice1=%v and slice2=%v, expected=%v, but got=%v", tc.slice1, tc.slice2, tc.expected, !tc.expected) + } + }) + } +} + +func TestIntersection(t *testing.T) { + testCases := []struct { + name string + slice1 []string + slice2 []string + expected []string + }{ + { + name: "when slice1 and slice2 have one common item then return that item", + slice1: []string{"test-gw1", "test-gw2"}, + slice2: []string{"test-gw1", "test-gw3", "test-gw4"}, + expected: []string{"test-gw1"}, + }, + { + name: "when slice1 and slice2 have no common item then return nil", + slice1: []string{"test-gw1", "test-gw2"}, + slice2: []string{"test-gw3", "test-gw4"}, + expected: nil, + }, + { + name: "when slice1 is empty then return nil", + slice1: []string{}, + slice2: []string{"test-gw3", "test-gw4"}, + expected: nil, + }, + { + name: "when slice2 is empty then return nil", + slice1: []string{"test-gw1", "test-gw2"}, + slice2: []string{}, + expected: nil, + }, + { + name: "when both slices are empty then return nil", + slice1: []string{}, + slice2: []string{}, + expected: nil, + }, + { + name: "when slice1 is nil then return nil", + slice2: []string{"test-gw3", "test-gw4"}, + expected: nil, + }, + { + name: "when slice2 is nil then return nil", + slice1: []string{"test-gw1", "test-gw2"}, + expected: nil, + }, + { + name: "when both slices are nil then return nil", + expected: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if r := Intersection(tc.slice1, tc.slice2); !reflect.DeepEqual(r, tc.expected) { + t.Errorf("expected=%v; got=%v", tc.expected, r) + } + }) + } +} + func TestMap(t *testing.T) { slice1 := []int{1, 2, 3, 4} f1 := func(x int) int { return x + 1 } @@ -446,7 +714,7 @@ func TestUnMarshallLimitNamespace(t *testing.T) { { name: "when namespace has no domain name then return correct values", namespace: "exampleNS/exampleGW#", - expectedKey: client.ObjectKey{"exampleNS", "exampleGW"}, + expectedKey: client.ObjectKey{Namespace: "exampleNS", Name: "exampleGW"}, expectedDomain: "", expectedError: false, }, @@ -491,49 +759,6 @@ func TestUnMarshallLimitNamespace(t *testing.T) { } } -func TestMarshallNamespace(t *testing.T) { - testCases := []struct { - name string - gwKey client.ObjectKey - domain string - expected string - }{ - { - name: "when input is valid then return expected output", - gwKey: client.ObjectKey{ - Namespace: "test", - Name: "myGwName", - }, - domain: "example.com", - expected: "test/myGwName#example.com", - }, - { - name: "when input is empty then return expected output", - gwKey: client.ObjectKey{}, - domain: "", - expected: "/#", - }, - { - name: "when input contains special characters then return expected output", - gwKey: client.ObjectKey{ - Namespace: "test", - Name: "myG.w-N*ame", - }, - domain: "example%-com", - expected: "test/myG.w-N*ame#example%-com", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - result := MarshallNamespace(tc.gwKey, tc.domain) - if !reflect.DeepEqual(result, tc.expected) { - t.Errorf("Expected %v, but got %v", tc.expected, result) - } - }) - } -} - func TestUnMarshallObjectKey(t *testing.T) { testCases := []struct { name string @@ -652,3 +877,51 @@ func TestHostnamesToStrings(t *testing.T) { }) } } + +func TestFilterValidSubdomains(t *testing.T) { + testCases := []struct { + name string + domains []gatewayapiv1beta1.Hostname + subdomains []gatewayapiv1beta1.Hostname + expected []gatewayapiv1beta1.Hostname + }{ + { + name: "when all subdomains are valid", + domains: []gatewayapiv1beta1.Hostname{"my-app.apps.io", "*.acme.com"}, + subdomains: []gatewayapiv1beta1.Hostname{"toystore.acme.com", "my-app.apps.io", "carstore.acme.com"}, + expected: []gatewayapiv1beta1.Hostname{"toystore.acme.com", "my-app.apps.io", "carstore.acme.com"}, + }, + { + name: "when some subdomains are valid and some are not", + domains: []gatewayapiv1beta1.Hostname{"my-app.apps.io", "*.acme.com"}, + subdomains: []gatewayapiv1beta1.Hostname{"toystore.acme.com", "my-app.apps.io", "other-app.apps.io"}, + expected: []gatewayapiv1beta1.Hostname{"toystore.acme.com", "my-app.apps.io"}, + }, + { + name: "when none of subdomains are valid", + domains: []gatewayapiv1beta1.Hostname{"my-app.apps.io", "*.acme.com"}, + subdomains: []gatewayapiv1beta1.Hostname{"other-app.apps.io"}, + expected: []gatewayapiv1beta1.Hostname{}, + }, + { + name: "when the set of super domains is empty", + domains: []gatewayapiv1beta1.Hostname{}, + subdomains: []gatewayapiv1beta1.Hostname{"toystore.acme.com"}, + expected: []gatewayapiv1beta1.Hostname{}, + }, + { + name: "when the set of subdomains is empty", + domains: []gatewayapiv1beta1.Hostname{"my-app.apps.io", "*.acme.com"}, + subdomains: []gatewayapiv1beta1.Hostname{}, + expected: []gatewayapiv1beta1.Hostname{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if r := FilterValidSubdomains(tc.domains, tc.subdomains); !reflect.DeepEqual(r, tc.expected) { + t.Errorf("expected=%v; got=%v", tc.expected, r) + } + }) + } +} diff --git a/pkg/common/gatewayapi_utils.go b/pkg/common/gatewayapi_utils.go index a527f64d9..ccb743336 100644 --- a/pkg/common/gatewayapi_utils.go +++ b/pkg/common/gatewayapi_utils.go @@ -91,7 +91,118 @@ func RulesFromHTTPRoute(route *gatewayapiv1beta1.HTTPRoute) []HTTPRouteRule { return rules } -func GetNamespaceFromPolicyTargetRef(ctx context.Context, cli client.Client, policy KuadrantPolicy) (string, error) { +type HTTPRouteRuleSelector struct { + *gatewayapiv1beta1.HTTPRouteMatch +} + +func (s *HTTPRouteRuleSelector) Selects(rule gatewayapiv1beta1.HTTPRouteRule) bool { + if s.HTTPRouteMatch == nil { + return true + } + + _, found := Find(rule.Matches, func(ruleMatch gatewayapiv1beta1.HTTPRouteMatch) bool { + // path + if s.Path != nil && !reflect.DeepEqual(s.Path, ruleMatch.Path) { + return false + } + + // method + if s.Method != nil && !reflect.DeepEqual(s.Method, ruleMatch.Method) { + return false + } + + // headers + for _, header := range s.Headers { + if _, found := Find(ruleMatch.Headers, func(otherHeader gatewayapiv1beta1.HTTPHeaderMatch) bool { + return reflect.DeepEqual(header, otherHeader) + }); !found { + return false + } + } + + // query params + for _, param := range s.QueryParams { + if _, found := Find(ruleMatch.QueryParams, func(otherParam gatewayapiv1beta1.HTTPQueryParamMatch) bool { + return reflect.DeepEqual(param, otherParam) + }); !found { + return false + } + } + + return true + }) + + return found +} + +// HTTPRouteRuleToString prints the matches of a HTTPRouteRule as string +func HTTPRouteRuleToString(rule gatewayapiv1beta1.HTTPRouteRule) string { + matches := Map(rule.Matches, HTTPRouteMatchToString) + return fmt.Sprintf("{matches:[%s]}", strings.Join(matches, ",")) +} + +func HTTPRouteMatchToString(match gatewayapiv1beta1.HTTPRouteMatch) string { + var patterns []string + if method := match.Method; method != nil { + patterns = append(patterns, fmt.Sprintf("method:%v", HTTPMethodToString(method))) + } + if path := match.Path; path != nil { + patterns = append(patterns, fmt.Sprintf("path:%s", HTTPPathMatchToString(path))) + } + if len(match.QueryParams) > 0 { + queryParams := Map(match.QueryParams, HTTPQueryParamMatchToString) + patterns = append(patterns, fmt.Sprintf("queryParams:[%s]", strings.Join(queryParams, ","))) + } + if len(match.Headers) > 0 { + headers := Map(match.Headers, HTTPHeaderMatchToString) + patterns = append(patterns, fmt.Sprintf("headers:[%s]", strings.Join(headers, ","))) + } + return fmt.Sprintf("{%s}", strings.Join(patterns, ",")) +} + +func HTTPPathMatchToString(path *gatewayapiv1beta1.HTTPPathMatch) string { + if path == nil { + return "*" + } + if path.Type != nil { + switch *path.Type { + case gatewayapiv1beta1.PathMatchExact: + return *path.Value + case gatewayapiv1beta1.PathMatchRegularExpression: + return fmt.Sprintf("~/%s/", *path.Value) + } + } + return fmt.Sprintf("%s*", *path.Value) +} + +func HTTPHeaderMatchToString(header gatewayapiv1beta1.HTTPHeaderMatch) string { + if header.Type != nil { + switch *header.Type { + case gatewayapiv1beta1.HeaderMatchRegularExpression: + return fmt.Sprintf("{%s:~/%s/}", header.Name, header.Value) + } + } + return fmt.Sprintf("{%s:%s}", header.Name, header.Value) +} + +func HTTPQueryParamMatchToString(queryParam gatewayapiv1beta1.HTTPQueryParamMatch) string { + if queryParam.Type != nil { + switch *queryParam.Type { + case gatewayapiv1beta1.QueryParamMatchRegularExpression: + return fmt.Sprintf("{%s:~/%s/}", queryParam.Name, queryParam.Value) + } + } + return fmt.Sprintf("{%s:%s}", queryParam.Name, queryParam.Value) +} + +func HTTPMethodToString(method *gatewayapiv1beta1.HTTPMethod) string { + if method == nil { + return "*" + } + return string(*method) +} + +func GetKuadrantNamespaceFromPolicyTargetRef(ctx context.Context, cli client.Client, policy KuadrantPolicy) (string, error) { targetRef := policy.GetTargetRef() gwNamespacedName := types.NamespacedName{Namespace: string(GetDefaultIfNil(targetRef.Namespace, policy.GetWrappedNamespace())), Name: string(targetRef.Name)} if IsTargetRefHTTPRoute(targetRef) { @@ -114,7 +225,7 @@ func GetNamespaceFromPolicyTargetRef(ctx context.Context, cli client.Client, pol return GetKuadrantNamespace(gw) } -func GetNamespaceFromPolicy(policy KuadrantPolicy) (string, bool) { +func GetKuadrantNamespaceFromPolicy(policy KuadrantPolicy) (string, bool) { if kuadrantNamespace, isSet := policy.GetAnnotations()[KuadrantNamespaceLabel]; isSet { return kuadrantNamespace, true } @@ -374,21 +485,36 @@ func (g GatewayWrapper) DeletePolicy(policyKey client.ObjectKey) bool { } // Hostnames builds a list of hostnames from the listeners. -func (g GatewayWrapper) Hostnames() []string { - hostnames := make([]string, 0) +func (g GatewayWrapper) Hostnames() []gatewayapiv1beta1.Hostname { + hostnames := make([]gatewayapiv1beta1.Hostname, 0) if g.Gateway == nil { return hostnames } for idx := range g.Spec.Listeners { if g.Spec.Listeners[idx].Hostname != nil { - hostnames = append(hostnames, string(*g.Spec.Listeners[idx].Hostname)) + hostnames = append(hostnames, *g.Spec.Listeners[idx].Hostname) } } return hostnames } +// GatewayWrapperList is a list of GatewayWrappers that implements sort.Interface +type GatewayWrapperList []GatewayWrapper + +func (g GatewayWrapperList) Len() int { + return len(g) +} + +func (g GatewayWrapperList) Less(i, j int) bool { + return g[i].CreationTimestamp.Before(&g[j].CreationTimestamp) +} + +func (g GatewayWrapperList) Swap(i, j int) { + g[i], g[j] = g[j], g[i] +} + // TargetHostnames returns an array of hostnames coming from the network object (HTTPRoute, Gateway) func TargetHostnames(targetNetworkObject client.Object) ([]string, error) { hosts := make([]string, 0) diff --git a/pkg/common/gatewayapi_utils_test.go b/pkg/common/gatewayapi_utils_test.go index d17ef8025..1f0668c22 100644 --- a/pkg/common/gatewayapi_utils_test.go +++ b/pkg/common/gatewayapi_utils_test.go @@ -241,6 +241,443 @@ func TestRulesFromHTTPRoute(t *testing.T) { } } +func TestHTTPRouteRuleSelectorSelects(t *testing.T) { + testCases := []struct { + name string + selector HTTPRouteRuleSelector + rule gatewayapiv1beta1.HTTPRouteRule + expected bool + }{ + { + name: "when the httproutrule contains the exact match then return true", + selector: HTTPRouteRuleSelector{ + HTTPRouteMatch: &gatewayapiv1beta1.HTTPRouteMatch{ + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodGet}[0], + Headers: []gatewayapiv1beta1.HTTPHeaderMatch{ + { + Type: &[]gatewayapiv1beta1.HeaderMatchType{gatewayapiv1beta1.HeaderMatchExact}[0], + Name: "someheader", + Value: "somevalue", + }, + }, + }, + }, + rule: gatewayapiv1beta1.HTTPRouteRule{ + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodGet}[0], + Headers: []gatewayapiv1beta1.HTTPHeaderMatch{ + { + Type: &[]gatewayapiv1beta1.HeaderMatchType{gatewayapiv1beta1.HeaderMatchExact}[0], + Name: "someheader", + Value: "somevalue", + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "when the httproutrule contains the exact match and more then return true", + selector: HTTPRouteRuleSelector{ + HTTPRouteMatch: &gatewayapiv1beta1.HTTPRouteMatch{ + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodGet}[0], + }, + }, + rule: gatewayapiv1beta1.HTTPRouteRule{ + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodGet}[0], + Headers: []gatewayapiv1beta1.HTTPHeaderMatch{ + { + Type: &[]gatewayapiv1beta1.HeaderMatchType{gatewayapiv1beta1.HeaderMatchExact}[0], + Name: "someheader", + Value: "somevalue", + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "when the httproutrule contains all the matching headers and more then return true", + selector: HTTPRouteRuleSelector{ + HTTPRouteMatch: &gatewayapiv1beta1.HTTPRouteMatch{ + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodGet}[0], + Headers: []gatewayapiv1beta1.HTTPHeaderMatch{ + { + Type: &[]gatewayapiv1beta1.HeaderMatchType{gatewayapiv1beta1.HeaderMatchExact}[0], + Name: "someheader", + Value: "somevalue", + }, + }, + }, + }, + rule: gatewayapiv1beta1.HTTPRouteRule{ + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodGet}[0], + Headers: []gatewayapiv1beta1.HTTPHeaderMatch{ + { + Type: &[]gatewayapiv1beta1.HeaderMatchType{gatewayapiv1beta1.HeaderMatchExact}[0], + Name: "someheader", + Value: "somevalue", + }, + { + Type: &[]gatewayapiv1beta1.HeaderMatchType{gatewayapiv1beta1.HeaderMatchRegularExpression}[0], + Name: "someotherheader", + Value: "someregex.*", + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "when the httproutrule contains an inexact match then return false", + selector: HTTPRouteRuleSelector{ + HTTPRouteMatch: &gatewayapiv1beta1.HTTPRouteMatch{ + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodGet}[0], + Headers: []gatewayapiv1beta1.HTTPHeaderMatch{ + { + Type: &[]gatewayapiv1beta1.HeaderMatchType{gatewayapiv1beta1.HeaderMatchExact}[0], + Name: "someheader", + Value: "somevalue", + }, + }, + }, + }, + rule: gatewayapiv1beta1.HTTPRouteRule{ + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodPost}[0], + Headers: []gatewayapiv1beta1.HTTPHeaderMatch{ + { + Type: &[]gatewayapiv1beta1.HeaderMatchType{gatewayapiv1beta1.HeaderMatchExact}[0], + Name: "someheader", + Value: "somevalue", + }, + }, + }, + }, + }, + expected: false, + }, + { + name: "when the httproutrule is empty then return false", + rule: gatewayapiv1beta1.HTTPRouteRule{}, + selector: HTTPRouteRuleSelector{ + HTTPRouteMatch: &gatewayapiv1beta1.HTTPRouteMatch{ + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodGet}[0], + }, + }, + expected: false, + }, + { + name: "when the selector is empty then return true", + selector: HTTPRouteRuleSelector{}, + rule: gatewayapiv1beta1.HTTPRouteRule{ + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodGet}[0], + Headers: []gatewayapiv1beta1.HTTPHeaderMatch{ + { + Type: &[]gatewayapiv1beta1.HeaderMatchType{gatewayapiv1beta1.HeaderMatchExact}[0], + Name: "someheader", + Value: "somevalue", + }, + }, + }, + }, + }, + expected: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if r := tc.selector.Selects(tc.rule); r != tc.expected { + expectedStr := "" + resultStr := "not" + if !tc.expected { + expectedStr = "not" + resultStr = "" + } + t.Error("expected selector", HTTPRouteMatchToString(*tc.selector.HTTPRouteMatch), expectedStr, "to select rule", HTTPRouteRuleToString(tc.rule), "but it does", resultStr) + } + }) + } +} + +func TestHTTPPathMatchToString(t *testing.T) { + testCases := []struct { + name string + input *gatewayapiv1beta1.HTTPPathMatch + expected string + }{ + { + name: "exact path match", + input: &[]gatewayapiv1beta1.HTTPPathMatch{ + { + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchExact}[0], + Value: &[]string{"/foo"}[0], + }, + }[0], + expected: "/foo", + }, + { + name: "regex path match", + input: &[]gatewayapiv1beta1.HTTPPathMatch{ + { + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchRegularExpression}[0], + Value: &[]string{"^\\/foo.*"}[0], + }, + }[0], + expected: "~/^\\/foo.*/", + }, + { + name: "path prefix match", + input: &[]gatewayapiv1beta1.HTTPPathMatch{ + { + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchPathPrefix}[0], + Value: &[]string{"/foo"}[0], + }, + }[0], + expected: "/foo*", + }, + { + name: "path match with default type", + input: &[]gatewayapiv1beta1.HTTPPathMatch{ + { + Value: &[]string{"/foo"}[0], + }, + }[0], + expected: "/foo*", + }, + { + name: "nil path match", + input: nil, + expected: "*", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if r := HTTPPathMatchToString(tc.input); r != tc.expected { + t.Errorf("expected: %s, got: %s", tc.expected, r) + } + }) + } +} + +func TestHTTPHeaderMatchToString(t *testing.T) { + testCases := []struct { + name string + input gatewayapiv1beta1.HTTPHeaderMatch + expected string + }{ + { + name: "exact header match", + input: gatewayapiv1beta1.HTTPHeaderMatch{ + Type: &[]gatewayapiv1beta1.HeaderMatchType{gatewayapiv1beta1.HeaderMatchExact}[0], + Name: "some-header", + Value: "foo", + }, + expected: "{some-header:foo}", + }, + { + name: "regex header match", + input: gatewayapiv1beta1.HTTPHeaderMatch{ + Type: &[]gatewayapiv1beta1.HeaderMatchType{gatewayapiv1beta1.HeaderMatchRegularExpression}[0], + Name: "some-header", + Value: "^foo.*", + }, + expected: "{some-header:~/^foo.*/}", + }, + { + name: "header match with default type", + input: gatewayapiv1beta1.HTTPHeaderMatch{ + Name: "some-header", + Value: "foo", + }, + expected: "{some-header:foo}", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if r := HTTPHeaderMatchToString(tc.input); r != tc.expected { + t.Errorf("expected: %s, got: %s", tc.expected, r) + } + }) + } +} + +func TestHTTPQueryParamMatchToString(t *testing.T) { + testCases := []struct { + name string + input gatewayapiv1beta1.HTTPQueryParamMatch + expected string + }{ + { + name: "exact query param match", + input: gatewayapiv1beta1.HTTPQueryParamMatch{ + Type: &[]gatewayapiv1beta1.QueryParamMatchType{gatewayapiv1beta1.QueryParamMatchExact}[0], + Name: "some-param", + Value: "foo", + }, + expected: "{some-param:foo}", + }, + { + name: "regex query param match", + input: gatewayapiv1beta1.HTTPQueryParamMatch{ + Type: &[]gatewayapiv1beta1.QueryParamMatchType{gatewayapiv1beta1.QueryParamMatchRegularExpression}[0], + Name: "some-param", + Value: "^foo.*", + }, + expected: "{some-param:~/^foo.*/}", + }, + { + name: "query param match with default type", + input: gatewayapiv1beta1.HTTPQueryParamMatch{ + Name: "some-param", + Value: "foo", + }, + expected: "{some-param:foo}", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if r := HTTPQueryParamMatchToString(tc.input); r != tc.expected { + t.Errorf("expected: %s, got: %s", tc.expected, r) + } + }) + } +} + +func TestHTTPMethodToString(t *testing.T) { + testCases := []struct { + input *gatewayapiv1beta1.HTTPMethod + expected string + }{ + { + input: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodGet}[0], + expected: "GET", + }, + { + input: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodHead}[0], + expected: "HEAD", + }, + { + input: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodPost}[0], + expected: "POST", + }, + { + input: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodPut}[0], + expected: "PUT", + }, + { + input: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodPatch}[0], + expected: "PATCH", + }, + { + input: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodDelete}[0], + expected: "DELETE", + }, + { + input: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodConnect}[0], + expected: "CONNECT", + }, + { + input: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodOptions}[0], + expected: "OPTIONS", + }, + { + input: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodTrace}[0], + expected: "TRACE", + }, + { + input: nil, + expected: "*", + }, + } + for _, tc := range testCases { + if r := HTTPMethodToString(tc.input); r != tc.expected { + t.Errorf("expected: %s, got: %s", tc.expected, r) + } + } +} + +func TestHTTPRouteMatchToString(t *testing.T) { + match := gatewayapiv1beta1.HTTPRouteMatch{ + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchExact}[0], + Value: &[]string{"/foo"}[0], + }, + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodGet}[0], + QueryParams: []gatewayapiv1beta1.HTTPQueryParamMatch{ + { + Type: &[]gatewayapiv1beta1.QueryParamMatchType{gatewayapiv1beta1.QueryParamMatchRegularExpression}[0], + Name: "page", + Value: "\\d+", + }, + }, + } + + expected := "{method:GET,path:/foo,queryParams:[{page:~/\\d+/}]}" + + if r := HTTPRouteMatchToString(match); r != expected { + t.Errorf("expected: %s, got: %s", expected, r) + } + + match.Headers = []gatewayapiv1beta1.HTTPHeaderMatch{ + { + Name: "x-foo", + Value: "bar", + }, + } + + expected = "{method:GET,path:/foo,queryParams:[{page:~/\\d+/}],headers:[{x-foo:bar}]}" + + if r := HTTPRouteMatchToString(match); r != expected { + t.Errorf("expected: %s, got: %s", expected, r) + } +} + +func TestHTTPRouteRuleToString(t *testing.T) { + rule := gatewayapiv1beta1.HTTPRouteRule{} + + expected := "{matches:[]}" + + if r := HTTPRouteRuleToString(rule); r != expected { + t.Errorf("expected: %s, got: %s", expected, r) + } + + rule.Matches = []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchExact}[0], + Value: &[]string{"/foo"}[0], + }, + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethodGet}[0], + QueryParams: []gatewayapiv1beta1.HTTPQueryParamMatch{ + { + Type: &[]gatewayapiv1beta1.QueryParamMatchType{gatewayapiv1beta1.QueryParamMatchRegularExpression}[0], + Name: "page", + Value: "\\d+", + }, + }, + }, + } + + expected = "{matches:[{method:GET,path:/foo,queryParams:[{page:~/\\d+/}]}]}" + + if r := HTTPRouteRuleToString(rule); r != expected { + t.Errorf("expected: %s, got: %s", expected, r) + } +} + func TestGatewaysMissingPolicyRef(t *testing.T) { gwList := &gatewayapiv1beta1.GatewayList{ Items: []gatewayapiv1beta1.Gateway{ diff --git a/pkg/reconcilers/targetref_reconciler.go b/pkg/reconcilers/targetref_reconciler.go index 124114194..a7fdcbde6 100644 --- a/pkg/reconcilers/targetref_reconciler.go +++ b/pkg/reconcilers/targetref_reconciler.go @@ -19,6 +19,7 @@ package reconcilers import ( "context" "fmt" + "sort" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/meta" @@ -94,6 +95,36 @@ func (r *TargetRefReconciler) FetchValidTargetRef(ctx context.Context, targetRef return nil, fmt.Errorf("FetchValidTargetRef: targetRef (%v) to unknown network resource", targetRef) } +// FetchAcceptedGatewayHTTPRoutes returns the list of HTTPRoutes that have been accepted as children of a gateway. +func (r *TargetRefReconciler) FetchAcceptedGatewayHTTPRoutes(ctx context.Context, gwKey client.ObjectKey) (routes []gatewayapiv1beta1.HTTPRoute) { + logger, _ := logr.FromContext(ctx) + logger = logger.WithName("FetchAcceptedGatewayHTTPRoutes").WithValues("gateway", gwKey) + + routeList := &gatewayapiv1beta1.HTTPRouteList{} + err := r.Client().List(ctx, routeList) + if err != nil { + logger.V(1).Info("failed to list httproutes", "err", err) + return + } + + for idx := range routeList.Items { + route := routeList.Items[idx] + routeParentStatus, found := common.Find(route.Status.RouteStatus.Parents, func(p gatewayapiv1beta1.RouteParentStatus) bool { + return *p.ParentRef.Kind == ("Gateway") && + ((p.ParentRef.Namespace == nil && route.GetNamespace() == gwKey.Namespace) || string(*p.ParentRef.Namespace) == gwKey.Namespace) && + string(p.ParentRef.Name) == gwKey.Name + }) + if found && meta.IsStatusConditionTrue(routeParentStatus.Conditions, "Accepted") { + logger.V(1).Info("found route attached to gateway", "httproute", client.ObjectKeyFromObject(&route)) + routes = append(routes, route) + continue + } + logger.V(1).Info("skipping route, not attached to gateway", "httproute", client.ObjectKeyFromObject(&route)) + } + + return +} + // TargetedGatewayKeys returns the list of gateways that are being referenced from the target. func (r *TargetRefReconciler) TargetedGatewayKeys(ctx context.Context, targetNetworkObject client.Object) []client.ObjectKey { switch obj := targetNetworkObject.(type) { @@ -167,6 +198,44 @@ func (r *TargetRefReconciler) DeleteTargetBackReference(ctx context.Context, pol return nil } +// GetAllGatewayPolicyRefs returns the policy refs of a given policy kind from all gateways managed by kuadrant. +// The gateway objects are handled in order of creation to mitigate the risk of non-idenpotent reconciliations based on +// this list of policy refs; nevertheless, the actual order of returned policy refs depends on the order the policy refs +// appear in the annotations of the gateways. +// Only gateways with status programmed are considered. +func (r *TargetRefReconciler) GetAllGatewayPolicyRefs(ctx context.Context, policyRefsConfig common.PolicyRefsConfig) ([]client.ObjectKey, error) { + var uniquePolicyRefs map[string]struct{} + var policyRefs []client.ObjectKey + + gwList := &gatewayapiv1beta1.GatewayList{} + if err := r.Client().List(ctx, gwList); err != nil { + return nil, err + } + + // sort the gateways by creation timestamp to mitigate the risk of non-idenpotent reconciliations + var gateways common.GatewayWrapperList + for i := range gwList.Items { + gateway := gwList.Items[i] + // skip gateways that are not managed by kuadrant or that are not ready + if !common.IsKuadrantManaged(&gateway) || meta.IsStatusConditionFalse(gateway.Status.Conditions, common.GatewayProgrammedConditionType) { + continue + } + gateways = append(gateways, common.GatewayWrapper{Gateway: &gateway, PolicyRefsConfig: policyRefsConfig}) + } + sort.Sort(gateways) + + for _, gw := range gateways { + for _, policyRef := range gw.PolicyRefs() { + if _, ok := uniquePolicyRefs[policyRef.String()]; ok { + continue + } + policyRefs = append(policyRefs, policyRef) + } + } + + return policyRefs, nil +} + // Returns: // * list of gateways to which the policy applies for the first time // * list of gateways to which the policy no longer applies diff --git a/pkg/rlptools/limit_index.go b/pkg/rlptools/limit_index.go deleted file mode 100644 index ee70d3f34..000000000 --- a/pkg/rlptools/limit_index.go +++ /dev/null @@ -1,271 +0,0 @@ -package rlptools - -import ( - "encoding/json" - "reflect" - "sort" - "strings" - - "github.com/go-logr/logr" - limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" - "sigs.k8s.io/controller-runtime/pkg/client" - - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" - "github.com/kuadrant/kuadrant-operator/pkg/common" -) - -type LimitsByDomain map[string][]kuadrantv1beta1.Limit - -func (l LimitsByDomain) String() string { - jsonData, _ := json.MarshalIndent(l, "", " ") - return string(jsonData) -} - -type LimitList []kuadrantv1beta1.Limit - -func (l LimitList) Len() int { - return len(l) -} - -func (l LimitList) Less(i, j int) bool { - if l[i].MaxValue != l[j].MaxValue { - return l[i].MaxValue > l[j].MaxValue - } - - if l[i].Seconds != l[j].Seconds { - return l[i].Seconds > l[j].Seconds - } - - // Conditions - - if len(l[i].Conditions) != len(l[j].Conditions) { - return len(l[i].Conditions) > len(l[j].Conditions) - } - - for idx, condI := range l[i].Conditions { - condJ := l[j].Conditions[idx] - switch strings.Compare(condI, condJ) { - case 1: - return true - case -1: - return false - } - } - - // Variables - - if len(l[i].Variables) != len(l[j].Variables) { - return len(l[i].Variables) > len(l[j].Variables) - } - - for idx, condI := range l[i].Variables { - condJ := l[j].Variables[idx] - switch strings.Compare(condI, condJ) { - case 1: - return true - case -1: - return false - } - } - - // they are equal. Return whatever - return true -} - -func (l LimitList) Swap(i, j int) { - l[i], l[j] = l[j], l[i] -} - -func SameLimitList(a, b []kuadrantv1beta1.Limit) bool { - if len(a) != len(b) { - return false - } - - aCopy := make([]kuadrantv1beta1.Limit, len(a)) - bCopy := make([]kuadrantv1beta1.Limit, len(b)) - - copy(aCopy, a) - copy(bCopy, b) - - // two limits with reordered conditions/variables are effectively the same - // For comparison purposes, nil equals the empty array for conditions and variables - for idx := range aCopy { - aCopy[idx].Conditions = common.GetEmptySliceIfNil(aCopy[idx].Conditions) - sort.Strings(aCopy[idx].Conditions) - - aCopy[idx].Variables = common.GetEmptySliceIfNil(aCopy[idx].Variables) - sort.Strings(aCopy[idx].Variables) - } - - for idx := range bCopy { - bCopy[idx].Conditions = common.GetEmptySliceIfNil(bCopy[idx].Conditions) - sort.Strings(bCopy[idx].Conditions) - - bCopy[idx].Variables = common.GetEmptySliceIfNil(bCopy[idx].Variables) - sort.Strings(bCopy[idx].Variables) - } - - sort.Sort(LimitList(aCopy)) - sort.Sort(LimitList(bCopy)) - - return reflect.DeepEqual(aCopy, bCopy) -} - -func (l LimitsByDomain) Equals(other LimitsByDomain) bool { - if other == nil { - return false - } - - if len(l) != len(other) { - return false - } - - for domain := range l { - if _, ok := other[domain]; !ok { - return false - } - - if !SameLimitList(l[domain], other[domain]) { - return false - } - } - - return true -} - -// LimitIndex allows manage Limitador CR limits based on gateways and domains -// gateways and domains are encoded in the namespace field of the limit -// limit namespace format: "{gateway}#{domain}" -type LimitIndex struct { - logger logr.Logger - gatewayLimits map[client.ObjectKey]LimitsByDomain -} - -func (l *LimitIndex) ToLimits() []limitadorv1alpha1.RateLimit { - limits := make([]limitadorv1alpha1.RateLimit, 0) - - for gwKey, limitsByDomain := range l.gatewayLimits { - for domain, limitList := range limitsByDomain { - for idx := range limitList { - // Currently, Limitador CRD v0.3.1, - // the fields "Variables" and "Conditions" are required - variables := common.GetEmptySliceIfNil(limitList[idx].Variables) - conditions := common.GetEmptySliceIfNil(limitList[idx].Conditions) - limits = append(limits, limitadorv1alpha1.RateLimit{ - Namespace: common.MarshallNamespace(gwKey, domain), - MaxValue: limitList[idx].MaxValue, - Seconds: limitList[idx].Seconds, - Variables: variables, - Conditions: conditions, - }) - } - } - } - - return limits -} - -func (l *LimitIndex) DeleteGateway(gwKey client.ObjectKey) { - delete(l.gatewayLimits, gwKey) -} - -func (l *LimitIndex) AddGatewayLimits(gwKey client.ObjectKey, gwLimits LimitsByDomain) { - for domain, limitList := range gwLimits { - for idx := range limitList { - l.AddLimit(gwKey, domain, &limitList[idx]) - } - } -} - -// AddLimit adds one new limit to the index structure -func (l *LimitIndex) AddLimit(gwKey client.ObjectKey, domain string, limit *kuadrantv1beta1.Limit) { - if _, ok := l.gatewayLimits[gwKey]; !ok { - l.gatewayLimits[gwKey] = make(LimitsByDomain) - } - - l.gatewayLimits[gwKey][domain] = append(l.gatewayLimits[gwKey][domain], *limit) -} - -// AddLimitFromRateLimit adds one new limit to the index structure -func (l *LimitIndex) AddLimitFromRateLimit(limit *limitadorv1alpha1.RateLimit) { - if limit == nil { - return - } - - gwKey, domain, err := common.UnMarshallLimitNamespace(limit.Namespace) - if err != nil { - l.logger.V(1).Info("cannot unmarshall limit namespace", - "namespace", limit.Namespace, - "error", err) - return - } - - l.AddLimit(gwKey, domain, kuadrantv1beta1.LimitFromLimitadorRateLimit(limit)) -} - -func (l *LimitIndex) Equals(other *LimitIndex) bool { - // reflect.DeepEqual does not work well with slices when order does not matter - - if other == nil { - return false - } - - if len(l.gatewayLimits) != len(other.gatewayLimits) { - return false - } - - for gwKey := range l.gatewayLimits { - if _, ok := other.gatewayLimits[gwKey]; !ok { - return false - } - - if !l.gatewayLimits[gwKey].Equals(other.gatewayLimits[gwKey]) { - return false - } - } - - return true -} - -// NewLimitadorIndex builds index to manage limits indexed by domain indexed by gateways -// yaml representation would be: -// -// --- -// gateway_key1: -// domain_1: -// - maxValue: X -// seconds: Y -// conditions: [ ... ] -// variables: [ ... ] -// domain_2: -// - maxValue: X -// seconds: Y -// conditions: [ ... ] -// variables: [ ... ] -// gateway_key2: -// domain_1: -// - maxValue: X -// seconds: Y -// conditions: [ ... ] -// variables: [ ... ] -// domain_2: -// - maxValue: X -// seconds: Y -// conditions: [ ... ] -// variables: [ ... ] -func NewLimitadorIndex(limitador *limitadorv1alpha1.Limitador, logger logr.Logger) *LimitIndex { - limitIdx := &LimitIndex{ - logger: logger, - gatewayLimits: make(map[client.ObjectKey]LimitsByDomain), - } - - if limitador == nil { - return limitIdx - } - - for idx := range limitador.Spec.Limits { - limitIdx.AddLimitFromRateLimit(&limitador.Spec.Limits[idx]) - } - - return limitIdx -} diff --git a/pkg/rlptools/limit_index_test.go b/pkg/rlptools/limit_index_test.go deleted file mode 100644 index b3e7826e4..000000000 --- a/pkg/rlptools/limit_index_test.go +++ /dev/null @@ -1,719 +0,0 @@ -//go:build unit - -package rlptools - -import ( - "os" - "reflect" - "testing" - - limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" - "github.com/kuadrant/kuadrant-operator/pkg/common" - "github.com/kuadrant/kuadrant-operator/pkg/log" -) - -func TestLimitIndexEquals(t *testing.T) { - logger := log.NewLogger( - log.WriteTo(os.Stdout), - log.SetLevel(log.DebugLevel), - ) - t.Run("nil indexes are equal", func(subT *testing.T) { - idxA := NewLimitadorIndex(nil, logger) - idxB := NewLimitadorIndex(nil, logger) - - if !idxA.Equals(idxB) { - subT.Fatal("nil indexes are not equal") - } - }) - t.Run("empty indexes are equal", func(subT *testing.T) { - idxA := NewLimitadorIndex(emptyLimitador(), logger) - idxB := NewLimitadorIndex(emptyLimitador(), logger) - - if !idxA.Equals(idxB) { - subT.Fatal("nil indexes are not equal") - } - }) - - // Rate limit order does not matter - // check the order does not matter when limit differ in - // maxValue, seconds, namespace, conditions, variables - testCases := []struct { - name string - limitador *limitadorv1alpha1.Limitador - }{ - { - "rate limit order does not matter: diff maxvalue", - limitadorWithMultipleLimitsMaxValue(), - }, - { - "rate limit order does not matter: diff seconds", - limitadorWithMultipleLimitsSeconds(), - }, - { - "rate limit order does not matter: diff namespace", - limitadorWithMultipleLimitsNamespace(), - }, - { - "rate limit order does not matter: diff conditions", - limitadorWithMultipleLimitsConditions(), - }, - { - "rate limit order does not matter: diff variables", - limitadorWithMultipleLimitsVariables(), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(subT *testing.T) { - limitadorA := tc.limitador - idxA := NewLimitadorIndex(limitadorA, logger) - reversedLimitadorA := *limitadorA - reversedLimitadorA.Spec.Limits = common.ReverseSlice(limitadorA.Spec.Limits) - - idxB := NewLimitadorIndex(&reversedLimitadorA, logger) - - if !idxA.Equals(idxB) { - subT.Fatal("indexes with reversed limits are not equal") - } - }) - } - - t.Run("limit conditions order does not matter", func(subT *testing.T) { - limitadorA := &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", - APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: []string{"a", "b"}, - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: make([]string, 0), - }, - }, - }, - } - idxA := NewLimitadorIndex(limitadorA, logger) - - limitadorB := &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", - APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "b", Namespace: "nsB"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: []string{"b", "a"}, // reverse order regarding limitadorA - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: make([]string, 0), - }, - }, - }, - } - idxB := NewLimitadorIndex(limitadorB, logger) - - if !idxA.Equals(idxB) { - subT.Fatal("indexes with limits with reversed conditions are not equal") - } - }) - - t.Run("limit variables order does not matter", func(subT *testing.T) { - limitadorA := &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", - APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: make([]string, 0), - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: []string{"a", "b"}, - }, - }, - }, - } - idxA := NewLimitadorIndex(limitadorA, logger) - - limitadorB := &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", - APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "b", Namespace: "nsB"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: make([]string, 0), - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: []string{"b", "a"}, // reverse order regarding limitadorA - }, - }, - }, - } - idxB := NewLimitadorIndex(limitadorB, logger) - - if !idxA.Equals(idxB) { - subT.Fatal("indexes with limits with reversed variables are not equal") - } - }) - - t.Run("nil or empty array does not matter", func(subT *testing.T) { - limitadorWithNilVariablesAndConditions := &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", - APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: nil, - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: nil, - }, - }, - }, - } - idxA := NewLimitadorIndex(limitadorWithNilVariablesAndConditions, logger) - - limitadorWithEmptyVariablesAndConditions := &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", - APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: []string{}, - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: []string{}, - }, - }, - }, - } - idxB := NewLimitadorIndex(limitadorWithEmptyVariablesAndConditions, logger) - - if !idxA.Equals(idxB) { - subT.Fatal("indexes with nil and empty arrays variables and conditions are not equal") - } - }) -} - -func TestLimitIndexToLimits(t *testing.T) { - logger := log.NewLogger( - log.WriteTo(os.Stdout), - log.SetLevel(log.DebugLevel), - ) - - t.Run("nil index return empty list", func(subT *testing.T) { - idx := NewLimitadorIndex(nil, logger) - - limits := idx.ToLimits() - if limits == nil { - subT.Fatal("returns nil") - } - if len(limits) != 0 { - subT.Fatal("returns not empty") - } - }) - - t.Run("empty index return empty list", func(subT *testing.T) { - idx := NewLimitadorIndex(emptyLimitador(), logger) - - limits := idx.ToLimits() - if limits == nil { - subT.Fatal("returns nil") - } - if len(limits) != 0 { - subT.Fatal("returns not empty") - } - }) - - t.Run("converting one limit index", func(subT *testing.T) { - limitador := &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: []string{"c_a", "c_b", "c_c"}, - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: []string{"v_a", "v_b", "v_c"}, - }, - }, - }, - } - idx := NewLimitadorIndex(limitador, logger) - - limits := idx.ToLimits() - if limits == nil { - subT.Fatal("returns nil") - } - if len(limits) != 1 { - subT.Fatal("it should return one limit") - } - - if !reflect.DeepEqual(limits[0], limitador.Spec.Limits[0]) { - subT.Fatal("limit does not match") - } - }) - t.Run("converting limits with nil variables", func(subT *testing.T) { - limitador := &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: []string{"c_a", "c_b", "c_c"}, - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: nil, - }, - }, - }, - } - idx := NewLimitadorIndex(limitador, logger) - - limits := idx.ToLimits() - if limits == nil { - subT.Fatal("returns nil") - } - if len(limits) != 1 { - subT.Fatal("it should return one limit") - } - - expectedLimit := limitador.Spec.Limits[0].DeepCopy() - // expected limitador limit should not have nil variables - expectedLimit.Variables = make([]string, 0) - if !reflect.DeepEqual(limits[0], *expectedLimit) { - subT.Fatal("limit does not match") - } - }) - - t.Run("converting limits with nil conditions", func(subT *testing.T) { - limitador := &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: nil, - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: []string{"v_a", "v_b", "v_c"}, - }, - }, - }, - } - idx := NewLimitadorIndex(limitador, logger) - - limits := idx.ToLimits() - if limits == nil { - subT.Fatal("returns nil") - } - if len(limits) != 1 { - subT.Fatal("it should return one limit") - } - - expectedLimit := limitador.Spec.Limits[0].DeepCopy() - // expected limitador limit should not have nil conditions - expectedLimit.Conditions = make([]string, 0) - if !reflect.DeepEqual(limits[0], *expectedLimit) { - subT.Fatal("limit does not match") - } - }) -} - -func TestLimitIndexAddLimit(t *testing.T) { - logger := log.NewLogger( - log.WriteTo(os.Stdout), - log.SetLevel(log.DebugLevel), - ) - - var ( - gwKey = client.ObjectKey{Name: "gwA", Namespace: "nsA"} - domain = "a.com" - maxValue = 2 - seconds = 19 - conditions = []string{"a", "b"} - variables = []string{"c", "d"} - ) - - limit := &kuadrantv1beta1.Limit{ - Conditions: conditions, MaxValue: maxValue, Seconds: seconds, Variables: variables, - } - - idx := NewLimitadorIndex(emptyLimitador(), logger) - idx.AddLimit(gwKey, domain, limit) - limits := idx.ToLimits() - if limits == nil { - t.Fatal("returns nil") - } - if len(limits) != 1 { - t.Fatal("it should return one limit") - } - - expectedLimit := limitadorv1alpha1.RateLimit{ - Conditions: conditions, - MaxValue: maxValue, - Namespace: common.MarshallNamespace(gwKey, domain), - Seconds: seconds, - Variables: variables, - } - if !reflect.DeepEqual(limits[0], expectedLimit) { - t.Fatal("limit does not match") - } -} - -func TestLimitIndexDeleteGateway(t *testing.T) { - logger := log.NewLogger( - log.WriteTo(os.Stdout), - log.SetLevel(log.DebugLevel), - ) - - t.Run("delete gateway in nil index is noop", func(subT *testing.T) { - idx := NewLimitadorIndex(nil, logger) - - gwKey := client.ObjectKey{Name: "gwA", Namespace: "nsA"} - idx.DeleteGateway(gwKey) - - limits := idx.ToLimits() - if limits == nil { - t.Fatal("returns nil") - } - if len(limits) != 0 { - subT.Fatal("returns not empty") - } - }) - - t.Run("delete gateway in empty index is noop", func(subT *testing.T) { - idx := NewLimitadorIndex(emptyLimitador(), logger) - - gwKey := client.ObjectKey{Name: "gwA", Namespace: "nsA"} - idx.DeleteGateway(gwKey) - - limits := idx.ToLimits() - if limits == nil { - t.Fatal("returns nil") - } - if len(limits) != 0 { - subT.Fatal("returns not empty") - } - }) - - t.Run("deleting missing gateway is noop", func(subT *testing.T) { - var ( - domain = "a.com" - gwKeyA = client.ObjectKey{Name: "gwA", Namespace: "nsA"} - gwKeyB = client.ObjectKey{Name: "gwB", Namespace: "nsB"} - ) - // has one limit with gwKeyA - limitador := &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - MaxValue: 1, - Namespace: common.MarshallNamespace(gwKeyA, domain), - Seconds: 1, - }, - }, - }, - } - - idx := NewLimitadorIndex(limitador, logger) - - // delete some gateway that does not exist in the index - idx.DeleteGateway(gwKeyB) - - limits := idx.ToLimits() - if limits == nil { - t.Fatal("returns nil") - } - if len(limits) != 1 { - subT.Fatal("it was expected to be one limit and none was deleted") - } - }) - - t.Run("index has one limit with the given gw key", func(subT *testing.T) { - var ( - domain = "a.com" - gwKeyA = client.ObjectKey{Name: "gwA", Namespace: "nsA"} - ) - // has one limit with gwKeyA - limitador := &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - MaxValue: 1, - Namespace: common.MarshallNamespace(gwKeyA, domain), - Seconds: 1, - }, - }, - }, - } - - idx := NewLimitadorIndex(limitador, logger) - - idx.DeleteGateway(gwKeyA) - - limits := idx.ToLimits() - if limits == nil { - t.Fatal("returns nil") - } - if len(limits) != 0 { - subT.Fatal("it was expected to be no limit") - } - }) - - t.Run("delete gateway does not delete more than necessary", func(subT *testing.T) { - var ( - domain = "a.com" - gwKeyA = client.ObjectKey{Name: "gwA", Namespace: "nsA"} - gwKeyB = client.ObjectKey{Name: "gwB", Namespace: "nsB"} - ) - // has three limits: two from gwKeyA and one from gwKeyB - limitador := &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - MaxValue: 1, - Namespace: common.MarshallNamespace(gwKeyA, domain), - Seconds: 1, - }, - { - MaxValue: 2, - Namespace: common.MarshallNamespace(gwKeyA, domain), - Seconds: 2, - }, - { - MaxValue: 1, - Namespace: common.MarshallNamespace(gwKeyB, domain), - Seconds: 1, - }, - }, - }, - } - - idx := NewLimitadorIndex(limitador, logger) - - idx.DeleteGateway(gwKeyA) - - limits := idx.ToLimits() - if limits == nil { - t.Fatal("returns nil") - } - if len(limits) != 1 { - subT.Fatal("it was expected to be one limit") - } - }) -} - -func limitadorNamespaceA() string { - return common.MarshallNamespace(client.ObjectKey{Name: "gwA", Namespace: "nsA"}, "a.com") -} - -func limitadorNamespaceB() string { - return common.MarshallNamespace(client.ObjectKey{Name: "gwB", Namespace: "nsB"}, "b.com") -} - -func emptyLimitador() *limitadorv1alpha1.Limitador { - return &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", - APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: nil, - }, - } -} - -func limitadorWithMultipleLimitsMaxValue() *limitadorv1alpha1.Limitador { - // limits differ in maxValue - return &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", - APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: nil, - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: nil, - }, - { - Conditions: nil, - MaxValue: 2, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: nil, - }, - }, - }, - } -} - -func limitadorWithMultipleLimitsSeconds() *limitadorv1alpha1.Limitador { - // limits differ in seconds - return &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", - APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: make([]string, 0), - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: make([]string, 0), - }, - { - Conditions: make([]string, 0), - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 2, - Variables: make([]string, 0), - }, - }, - }, - } -} - -func limitadorWithMultipleLimitsNamespace() *limitadorv1alpha1.Limitador { - // limits differ in namespace - return &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", - APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: make([]string, 0), - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: make([]string, 0), - }, - { - Conditions: make([]string, 0), - MaxValue: 1, - Namespace: limitadorNamespaceB(), - Seconds: 1, - Variables: make([]string, 0), - }, - }, - }, - } -} - -func limitadorWithMultipleLimitsConditions() *limitadorv1alpha1.Limitador { - // limits differ in conditions - return &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", - APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: []string{"a"}, - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: make([]string, 0), - }, - { - Conditions: []string{"b"}, - MaxValue: 1, - Namespace: limitadorNamespaceB(), - Seconds: 1, - Variables: make([]string, 0), - }, - }, - }, - } -} - -func limitadorWithMultipleLimitsVariables() *limitadorv1alpha1.Limitador { - // limits differ in variables - return &limitadorv1alpha1.Limitador{ - TypeMeta: metav1.TypeMeta{ - Kind: "Limitador", - APIVersion: "limitador.kuadrant.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "nsA"}, - Spec: limitadorv1alpha1.LimitadorSpec{ - Limits: []limitadorv1alpha1.RateLimit{ - { - Conditions: make([]string, 0), - MaxValue: 1, - Namespace: limitadorNamespaceA(), - Seconds: 1, - Variables: []string{"a"}, - }, - { - Conditions: make([]string, 0), - MaxValue: 1, - Namespace: limitadorNamespaceB(), - Seconds: 1, - Variables: []string{"b"}, - }, - }, - }, - } -} diff --git a/pkg/rlptools/rate_limit_index.go b/pkg/rlptools/rate_limit_index.go new file mode 100644 index 000000000..6c41816e4 --- /dev/null +++ b/pkg/rlptools/rate_limit_index.go @@ -0,0 +1,130 @@ +package rlptools + +import ( + "reflect" + "sort" + "strings" + + orderedmap "github.com/elliotchance/orderedmap/v2" + limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kuadrant/kuadrant-operator/pkg/common" +) + +type RateLimitIndexKey = client.ObjectKey + +// NewRateLimitIndex builds an index to manage sets of rate limits, organized by key +func NewRateLimitIndex() *RateLimitIndex { + return &RateLimitIndex{*orderedmap.NewOrderedMap[RateLimitIndexKey, RateLimitList]()} +} + +// RateLimitIndex stores RateLimitLists by key +type RateLimitIndex struct { + orderedmap.OrderedMap[RateLimitIndexKey, RateLimitList] +} + +func (l *RateLimitIndex) Set(key RateLimitIndexKey, rateLimits RateLimitList) { + if len(rateLimits) == 0 { + return + } + l.OrderedMap.Set(key, rateLimits) +} + +func (l *RateLimitIndex) ToRateLimits() RateLimitList { + limitadorRateLimits := make(RateLimitList, 0) + for rlSet := l.Front(); rlSet != nil; rlSet = rlSet.Next() { + limitadorRateLimits = append(limitadorRateLimits, rlSet.Value...) + } + return limitadorRateLimits +} + +type RateLimitList []limitadorv1alpha1.RateLimit + +func (l RateLimitList) Len() int { + return len(l) +} + +func (l RateLimitList) Less(i, j int) bool { + if l[i].MaxValue != l[j].MaxValue { + return l[i].MaxValue > l[j].MaxValue + } + + if l[i].Seconds != l[j].Seconds { + return l[i].Seconds > l[j].Seconds + } + + // Conditions + + if len(l[i].Conditions) != len(l[j].Conditions) { + return len(l[i].Conditions) > len(l[j].Conditions) + } + + for idx, condI := range l[i].Conditions { + condJ := l[j].Conditions[idx] + switch strings.Compare(condI, condJ) { + case 1: + return true + case -1: + return false + } + } + + // Variables + + if len(l[i].Variables) != len(l[j].Variables) { + return len(l[i].Variables) > len(l[j].Variables) + } + + for idx, condI := range l[i].Variables { + condJ := l[j].Variables[idx] + switch strings.Compare(condI, condJ) { + case 1: + return true + case -1: + return false + } + } + + // they are equal. Return whatever + return true +} + +func (l RateLimitList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +func Equal(a, b RateLimitList) bool { + if len(a) != len(b) { + return false + } + + aCopy := make(RateLimitList, len(a)) + bCopy := make(RateLimitList, len(b)) + + copy(aCopy, a) + copy(bCopy, b) + + // two limits with reordered conditions/variables are effectively the same + // For comparison purposes, nil equals the empty array for conditions and variables + for idx := range aCopy { + aCopy[idx].Conditions = common.GetEmptySliceIfNil(aCopy[idx].Conditions) + sort.Strings(aCopy[idx].Conditions) + + aCopy[idx].Variables = common.GetEmptySliceIfNil(aCopy[idx].Variables) + sort.Strings(aCopy[idx].Variables) + } + + for idx := range bCopy { + bCopy[idx].Conditions = common.GetEmptySliceIfNil(bCopy[idx].Conditions) + sort.Strings(bCopy[idx].Conditions) + + bCopy[idx].Variables = common.GetEmptySliceIfNil(bCopy[idx].Variables) + sort.Strings(bCopy[idx].Variables) + } + + sort.Sort(aCopy) + sort.Sort(bCopy) + + return reflect.DeepEqual(aCopy, bCopy) +} diff --git a/pkg/rlptools/rate_limit_index_test.go b/pkg/rlptools/rate_limit_index_test.go new file mode 100644 index 000000000..e418f03fd --- /dev/null +++ b/pkg/rlptools/rate_limit_index_test.go @@ -0,0 +1,140 @@ +//go:build unit + +package rlptools + +import ( + "reflect" + "testing" + + limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestRateLimitIndexSet(t *testing.T) { + t.Run("index rate limits to a key", func(subT *testing.T) { + index := NewRateLimitIndex() + + index.Set(client.ObjectKey{Name: "rlp-1", Namespace: "ns"}, []limitadorv1alpha1.RateLimit{ + {Namespace: "ns/rlp-1", MaxValue: 10, Seconds: 1}, + {Namespace: "ns/rlp-1", MaxValue: 100, Seconds: 60}, + {Namespace: "ns/rlp-1", MaxValue: 1000, Seconds: 1}, + }) + + aggregatedRateLimits := index.ToRateLimits() + expectedCount := 3 + if len(aggregatedRateLimits) != expectedCount { + subT.Fatal("expected:", expectedCount, "rate limits, returned:", len(aggregatedRateLimits)) + } + }) + + t.Run("index rate limits to different keys", func(subT *testing.T) { + index := NewRateLimitIndex() + + index.Set(client.ObjectKey{Name: "rlp-1", Namespace: "ns"}, []limitadorv1alpha1.RateLimit{ + {Namespace: "ns/rlp-1", MaxValue: 10, Seconds: 1}, + {Namespace: "ns/rlp-1", MaxValue: 100, Seconds: 60}, + {Namespace: "ns/rlp-1", MaxValue: 1000, Seconds: 1}, + }) + + index.Set(client.ObjectKey{Name: "rlp-2", Namespace: "ns"}, []limitadorv1alpha1.RateLimit{ + {Namespace: "ns/rlp-2", MaxValue: 50, Seconds: 1}, + }) + + key := client.ObjectKey{Name: "rlp-1", Namespace: "ns"} + rateLimits, found := index.Get(key) + if !found { + subT.Fatal("expected rate limits to be indexed to key but none found:", key) + } + expectedCount := 3 + if len(rateLimits) != expectedCount { + subT.Fatal("expected:", expectedCount, "rate limits for key", key, ", returned:", len(rateLimits)) + } + + key = client.ObjectKey{Name: "rlp-2", Namespace: "ns"} + rateLimits, found = index.Get(key) + if !found { + subT.Fatal("expected rate limits to be indexed to key but none found:", key) + } + expectedCount = 1 + if len(rateLimits) != expectedCount { + subT.Fatal("expected:", expectedCount, "rate limits for key", key, ", returned:", len(rateLimits)) + } + + aggregatedRateLimits := index.ToRateLimits() + expectedCount = 4 + if len(aggregatedRateLimits) != expectedCount { + subT.Fatal("expected:", expectedCount, "rate limits in total, returned:", len(aggregatedRateLimits)) + } + }) + + t.Run("reset rate limits for an existing key", func(subT *testing.T) { + index := NewRateLimitIndex() + + index.Set(client.ObjectKey{Name: "rlp-1", Namespace: "ns"}, []limitadorv1alpha1.RateLimit{ + {Namespace: "ns/rlp-1", MaxValue: 10, Seconds: 1}, + {Namespace: "ns/rlp-1", MaxValue: 100, Seconds: 60}, + {Namespace: "ns/rlp-1", MaxValue: 1000, Seconds: 1}, + }) + + index.Set(client.ObjectKey{Name: "rlp-1", Namespace: "ns"}, []limitadorv1alpha1.RateLimit{ + {Namespace: "ns/rlp-1", MaxValue: 500, Seconds: 3600}, + }) + + aggregatedRateLimits := index.ToRateLimits() + expectedCount := 1 + if len(aggregatedRateLimits) != expectedCount { + subT.Fatal("expected:", expectedCount, "rate limits, returned:", len(aggregatedRateLimits)) + } + if !reflect.DeepEqual(aggregatedRateLimits[0], limitadorv1alpha1.RateLimit{Namespace: "ns/rlp-1", MaxValue: 500, Seconds: 3600}) { + subT.Fatal("expected rate limit to be equal to the last one set") + } + }) + + t.Run("add an empty list of limits if a noop", func(subT *testing.T) { + idx := NewRateLimitIndex() + + idx.Set(client.ObjectKey{Name: "gwA", Namespace: "nsA"}, []limitadorv1alpha1.RateLimit{}) + + aggregatedRateLimits := idx.ToRateLimits() + if len(aggregatedRateLimits) != 0 { + subT.Fatal("returns not empty") + } + }) + + t.Run("add nil list of limits if a noop", func(subT *testing.T) { + idx := NewRateLimitIndex() + + idx.Set(client.ObjectKey{Name: "gwA", Namespace: "nsA"}, []limitadorv1alpha1.RateLimit{}) + + aggregatedRateLimits := idx.ToRateLimits() + if len(aggregatedRateLimits) != 0 { + subT.Fatal("returns not empty") + } + }) +} + +func TestRateLimitIndexToRateLimits(t *testing.T) { + t.Run("nil index return empty list", func(subT *testing.T) { + idx := NewRateLimitIndex() + + limits := idx.ToRateLimits() + if limits == nil { + subT.Fatal("returns nil") + } + if len(limits) != 0 { + subT.Fatal("returns not empty") + } + }) + + t.Run("empty index return empty list", func(subT *testing.T) { + idx := NewRateLimitIndex() + + limits := idx.ToRateLimits() + if limits == nil { + subT.Fatal("returns nil") + } + if len(limits) != 0 { + subT.Fatal("returns not empty") + } + }) +} diff --git a/pkg/rlptools/utils.go b/pkg/rlptools/utils.go new file mode 100644 index 000000000..ae0163617 --- /dev/null +++ b/pkg/rlptools/utils.go @@ -0,0 +1,90 @@ +package rlptools + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "unicode" + + limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" + + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" + "github.com/kuadrant/kuadrant-operator/pkg/common" +) + +const ( + LimitadorRateLimitIdentitiferPrefix = "limit." +) + +func LimitNameToLimitadorIdentifier(uniqueLimitName string) string { + identifier := LimitadorRateLimitIdentitiferPrefix + + // sanitize chars that are not allowed in limitador identifiers + for _, c := range uniqueLimitName { + if unicode.IsLetter(c) || unicode.IsDigit(c) || c == '_' { + identifier += string(c) + } else { + identifier += "_" + } + } + + // to avoid breaking the uniqueness of the limit name after sanitization, we add a hash of the original name + hash := sha256.Sum256([]byte(uniqueLimitName)) + identifier += "__" + hex.EncodeToString(hash[:4]) + + return identifier +} + +// LimitadorRateLimitsFromRLP converts rate limits from a Kuadrant RateLimitPolicy into a list of Limitador rate limit +// objects +func LimitadorRateLimitsFromRLP(rlp *kuadrantv1beta2.RateLimitPolicy) []limitadorv1alpha1.RateLimit { + limitsNamespace := LimitsNamespaceFromRLP(rlp) + + rateLimits := make([]limitadorv1alpha1.RateLimit, 0) + for limitKey, limit := range rlp.Spec.Limits { + limitIdentifier := LimitNameToLimitadorIdentifier(limitKey) + for _, rate := range limit.Rates { + maxValue, seconds := rateToSeconds(rate) + rateLimits = append(rateLimits, limitadorv1alpha1.RateLimit{ + Namespace: limitsNamespace, + MaxValue: maxValue, + Seconds: seconds, + Conditions: []string{fmt.Sprintf("%s == \"1\"", limitIdentifier)}, + Variables: common.GetEmptySliceIfNil(limit.CountersAsStringList()), + }) + } + } + return rateLimits +} + +func LimitsNamespaceFromRLP(rlp *kuadrantv1beta2.RateLimitPolicy) string { + return fmt.Sprintf("%s/%s", rlp.GetNamespace(), rlp.GetName()) +} + +var timeUnitMap = map[kuadrantv1beta2.TimeUnit]int{ + kuadrantv1beta2.TimeUnit("second"): 1, + kuadrantv1beta2.TimeUnit("minute"): 60, + kuadrantv1beta2.TimeUnit("hour"): 60 * 60, + kuadrantv1beta2.TimeUnit("day"): 60 * 60 * 24, +} + +// rateToSeconds converts from RLP Rate API (limit, duration and unit) +// to Limitador's Limit format (maxValue, Seconds) +func rateToSeconds(rate kuadrantv1beta2.Rate) (maxValue int, seconds int) { + maxValue = rate.Limit + seconds = 0 + + if tmpSecs, ok := timeUnitMap[rate.Unit]; ok && rate.Duration > 0 { + seconds = tmpSecs * rate.Duration + } + + if rate.Duration < 0 { + seconds = 0 + } + + if rate.Limit < 0 { + maxValue = 0 + } + + return +} diff --git a/pkg/rlptools/utils_test.go b/pkg/rlptools/utils_test.go new file mode 100644 index 000000000..0b85796fa --- /dev/null +++ b/pkg/rlptools/utils_test.go @@ -0,0 +1,365 @@ +//go:build unit + +package rlptools + +import ( + "reflect" + "regexp" + "testing" + + limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" + "github.com/kuadrant/kuadrant-operator/pkg/common" +) + +func testRLP_1Limit_1Rate(ns, name string) *kuadrantv1beta2.RateLimitPolicy { + return &kuadrantv1beta2.RateLimitPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "RateLimitPolicy", + APIVersion: kuadrantv1beta2.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + Limits: map[string]kuadrantv1beta2.Limit{ + "l1": { + Rates: []kuadrantv1beta2.Rate{ + { + Limit: 5, + Duration: 10, + Unit: kuadrantv1beta2.TimeUnit("second"), + }, + }, + }, + }, + }, + } +} + +func testRLP_2Limits_1Rate(ns, name string) *kuadrantv1beta2.RateLimitPolicy { + return &kuadrantv1beta2.RateLimitPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "RateLimitPolicy", + APIVersion: kuadrantv1beta2.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + Limits: map[string]kuadrantv1beta2.Limit{ + "l1": { + Rates: []kuadrantv1beta2.Rate{ + { + Limit: 5, + Duration: 10, + Unit: kuadrantv1beta2.TimeUnit("second"), + }, + }, + }, + "l2": { + Rates: []kuadrantv1beta2.Rate{ + { + Limit: 3, + Duration: 1, + Unit: kuadrantv1beta2.TimeUnit("hour"), + }, + }, + }, + }, + }, + } +} + +func testRLP_1Limit_2Rates(ns, name string) *kuadrantv1beta2.RateLimitPolicy { + return &kuadrantv1beta2.RateLimitPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "RateLimitPolicy", + APIVersion: kuadrantv1beta2.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + Limits: map[string]kuadrantv1beta2.Limit{ + "l1": { + Rates: []kuadrantv1beta2.Rate{ + { + Limit: 5, + Duration: 10, + Unit: kuadrantv1beta2.TimeUnit("second"), + }, + { + Limit: 3, + Duration: 1, + Unit: kuadrantv1beta2.TimeUnit("minute"), + }, + }, + }, + }, + }, + } +} + +func testRLP_1Limit_1Rate_1Counter(ns, name string) *kuadrantv1beta2.RateLimitPolicy { + return &kuadrantv1beta2.RateLimitPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "RateLimitPolicy", + APIVersion: kuadrantv1beta2.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + Limits: map[string]kuadrantv1beta2.Limit{ + "l1": { + Counters: []kuadrantv1beta2.ContextSelector{ + kuadrantv1beta2.ContextSelector("request.path"), + }, + Rates: []kuadrantv1beta2.Rate{ + { + Limit: 5, + Duration: 10, + Unit: kuadrantv1beta2.TimeUnit("second"), + }, + }, + }, + }, + }, + } +} + +func TestLimitNameToLimitadorIdentifier(t *testing.T) { + testCases := []struct { + name string + input string + expected *regexp.Regexp + }{ + { + name: "prepends the limitador limit identifier prefix", + input: "foo", + expected: regexp.MustCompile(`^limit\.foo.+`), + }, + { + name: "sanitizes invalid chars", + input: "my/limit-0", + expected: regexp.MustCompile(`^limit\.my_limit_0.+$`), + }, + { + name: "sanitizes the dot char (.) even though it is a valid char in limitador identifiers", + input: "my.limit", + expected: regexp.MustCompile(`^limit\.my_limit.+$`), + }, + { + name: "appends a hash of the original name to avoid breaking uniqueness", + input: "foo", + expected: regexp.MustCompile(`^.+__2c26b46b$`), + }, + { + name: "empty string", + input: "", + expected: regexp.MustCompile(`^limit.__e3b0c442$`), + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(subT *testing.T) { + identifier := LimitNameToLimitadorIdentifier(tc.input) + if !tc.expected.MatchString(identifier) { + subT.Errorf("identifier does not match, expected(%s), got (%s)", tc.expected, identifier) + } + }) + } +} + +func TestLimitadorRateLimitsFromRLP(t *testing.T) { + testCases := []struct { + name string + rlp *kuadrantv1beta2.RateLimitPolicy + expected []limitadorv1alpha1.RateLimit + }{ + { + name: "basic: 1 limit, 1 rate", + rlp: testRLP_1Limit_1Rate("testNS", "rlpA"), + expected: []limitadorv1alpha1.RateLimit{ + { + Namespace: "testNS/rlpA", + MaxValue: 5, + Seconds: 10, + Conditions: []string{`limit.l1__2804bad6 == "1"`}, + Variables: []string{}, + }, + }, + }, + { + name: "multiple limits: 2 limits with 1 rate each", + rlp: testRLP_2Limits_1Rate("testNS", "rlpA"), + expected: []limitadorv1alpha1.RateLimit{ + { + Namespace: "testNS/rlpA", + MaxValue: 5, + Seconds: 10, + Conditions: []string{`limit.l1__2804bad6 == "1"`}, + Variables: []string{}, + }, + { + Namespace: "testNS/rlpA", + MaxValue: 3, + Seconds: 3600, + Conditions: []string{`limit.l2__8a1cee43 == "1"`}, + Variables: []string{}, + }, + }, + }, + { + name: "multiple rates: 1 limit with 2 rates", + rlp: testRLP_1Limit_2Rates("testNS", "rlpA"), + expected: []limitadorv1alpha1.RateLimit{ + { + Namespace: "testNS/rlpA", + MaxValue: 5, + Seconds: 10, + Conditions: []string{`limit.l1__2804bad6 == "1"`}, + Variables: []string{}, + }, + { + Namespace: "testNS/rlpA", + MaxValue: 3, + Seconds: 60, + Conditions: []string{`limit.l1__2804bad6 == "1"`}, + Variables: []string{}, + }, + }, + }, + { + name: "basic: 1 limit, 1 rate", + rlp: testRLP_1Limit_1Rate_1Counter("testNS", "rlpA"), + expected: []limitadorv1alpha1.RateLimit{ + { + Namespace: "testNS/rlpA", + MaxValue: 5, + Seconds: 10, + Conditions: []string{`limit.l1__2804bad6 == "1"`}, + Variables: []string{"request.path"}, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(subT *testing.T) { + rateLimits := LimitadorRateLimitsFromRLP(tc.rlp) + // Instead of sorting to compare, check len and then iterate + if len(rateLimits) != len(tc.expected) { + subT.Errorf("expected limits len (%d), got (%d)", len(tc.expected), len(rateLimits)) + } + // When both slices have equal length, items can be checked one by one. + for _, rl := range rateLimits { + if _, found := common.Find(tc.expected, func(expectedRateLimit limitadorv1alpha1.RateLimit) bool { + return reflect.DeepEqual(rl, expectedRateLimit) + }); !found { + subT.Errorf("returned rate limit (%+v) not within expected ones, expected: %v", rl, tc.expected) + } + } + }) + } +} + +func TestConvertRateIntoSeconds(t *testing.T) { + testCases := []struct { + name string + rate kuadrantv1beta2.Rate + expectedMaxValue int + expectedSeconds int + }{ + { + name: "seconds", + rate: kuadrantv1beta2.Rate{ + Limit: 5, Duration: 2, Unit: kuadrantv1beta2.TimeUnit("second"), + }, + expectedMaxValue: 5, + expectedSeconds: 2, + }, + { + name: "minutes", + rate: kuadrantv1beta2.Rate{ + Limit: 5, Duration: 2, Unit: kuadrantv1beta2.TimeUnit("minute"), + }, + expectedMaxValue: 5, + expectedSeconds: 2 * 60, + }, + { + name: "hours", + rate: kuadrantv1beta2.Rate{ + Limit: 5, Duration: 2, Unit: kuadrantv1beta2.TimeUnit("hour"), + }, + expectedMaxValue: 5, + expectedSeconds: 2 * 60 * 60, + }, + { + name: "day", + rate: kuadrantv1beta2.Rate{ + Limit: 5, Duration: 2, Unit: kuadrantv1beta2.TimeUnit("day"), + }, + expectedMaxValue: 5, + expectedSeconds: 2 * 60 * 60 * 24, + }, + { + name: "negative limit", + rate: kuadrantv1beta2.Rate{ + Limit: -5, Duration: 2, Unit: kuadrantv1beta2.TimeUnit("second"), + }, + expectedMaxValue: 0, + expectedSeconds: 2, + }, + { + name: "negative duration", + rate: kuadrantv1beta2.Rate{ + Limit: 5, Duration: -2, Unit: kuadrantv1beta2.TimeUnit("second"), + }, + expectedMaxValue: 5, + expectedSeconds: 0, + }, + { + name: "limit is 0", + rate: kuadrantv1beta2.Rate{ + Limit: 0, Duration: 2, Unit: kuadrantv1beta2.TimeUnit("second"), + }, + expectedMaxValue: 0, + expectedSeconds: 2, + }, + { + name: "rate is 0", + rate: kuadrantv1beta2.Rate{ + Limit: 5, Duration: 0, Unit: kuadrantv1beta2.TimeUnit("second"), + }, + expectedMaxValue: 5, + expectedSeconds: 0, + }, + { + name: "unexpected time unit", + rate: kuadrantv1beta2.Rate{ + Limit: 5, Duration: 2, Unit: kuadrantv1beta2.TimeUnit("unknown"), + }, + expectedMaxValue: 5, + expectedSeconds: 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(subT *testing.T) { + maxValue, seconds := rateToSeconds(tc.rate) + if maxValue != tc.expectedMaxValue { + subT.Errorf("maxValue does not match, expected(%d), got (%d)", tc.expectedMaxValue, maxValue) + } + if seconds != tc.expectedSeconds { + subT.Errorf("seconds does not match, expected(%d), got (%d)", tc.expectedSeconds, seconds) + } + }) + } +} diff --git a/pkg/rlptools/wasm/types.go b/pkg/rlptools/wasm/types.go new file mode 100644 index 000000000..b53a34745 --- /dev/null +++ b/pkg/rlptools/wasm/types.go @@ -0,0 +1,116 @@ +package wasm + +import ( + "encoding/json" + + _struct "github.com/golang/protobuf/ptypes/struct" + gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" +) + +var ( + PathMatchTypeMap = map[gatewayapiv1beta1.PathMatchType]PatternOperator{ + gatewayapiv1beta1.PathMatchExact: PatternOperator(kuadrantv1beta2.EqualOperator), + gatewayapiv1beta1.PathMatchPathPrefix: PatternOperator(kuadrantv1beta2.StartsWithOperator), + gatewayapiv1beta1.PathMatchRegularExpression: PatternOperator(kuadrantv1beta2.MatchesOperator), + } +) + +type SelectorSpec struct { + // Selector of an attribute from the contextual properties provided by kuadrant + // during request and connection processing + Selector kuadrantv1beta2.ContextSelector `json:"selector"` + + // If not set it defaults to `selector` field value as the descriptor key. + // +optional + Key *string `json:"key,omitempty"` + + // An optional value to use if the selector is not found in the context. + // If not set and the selector is not found in the context, then no descriptor is generated. + // +optional + Default *string `json:"default,omitempty"` +} + +type StaticSpec struct { + Value string `json:"value"` + Key string `json:"key"` +} + +// TODO implement one of constraint +// Precisely one of "static", "selector" must be set. +type DataItem struct { + // +optional + Static *StaticSpec `json:"static,omitempty"` + + // +optional + Selector *SelectorSpec `json:"selector,omitempty"` +} + +type PatternOperator kuadrantv1beta2.WhenConditionOperator + +type PatternExpression struct { + // Selector of an attribute from the contextual properties provided by kuadrant + // during request and connection processing + Selector kuadrantv1beta2.ContextSelector `json:"selector"` + + // The binary operator to be applied to the content fetched from context, for comparison with "value". + // Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + // TODO build comprehensive list of operators + Operator PatternOperator `json:"operator"` + + // The value of reference for the comparison with the content fetched from the context. + Value string `json:"value"` +} + +// Condition defines traffic matching rules +type Condition struct { + // All of the expressions defined must match to match this condition + // +optional + AllOf []PatternExpression `json:"allOf,omitempty"` +} + +// Rule defines one rate limit configuration. When conditions are met, +// it uses `data` section to generate one RLS descriptor. +type Rule struct { + // +optional + Conditions []Condition `json:"conditions,omitempty"` + // +optional + Data []DataItem `json:"data,omitempty"` +} + +type RateLimitPolicy struct { + Name string `json:"name"` + Domain string `json:"domain"` + Service string `json:"service"` + Hostnames []string `json:"hostnames"` + + // +optional + Rules []Rule `json:"rules,omitempty"` +} + +// +kubebuilder:validation:Enum:=deny;allow +type FailureModeType string + +const ( + FailureModeDeny FailureModeType = "deny" + FailureModeAllow FailureModeType = "allow" +) + +type Plugin struct { + FailureMode FailureModeType `json:"failureMode"` + RateLimitPolicies []RateLimitPolicy `json:"rateLimitPolicies"` +} + +func (w *Plugin) ToStruct() (*_struct.Struct, error) { + wasmPluginJSON, err := json.Marshal(w) + if err != nil { + return nil, err + } + + pluginConfigStruct := &_struct.Struct{} + if err := pluginConfigStruct.UnmarshalJSON(wasmPluginJSON); err != nil { + return nil, err + } + return pluginConfigStruct, nil +} diff --git a/pkg/rlptools/wasm_utils.go b/pkg/rlptools/wasm_utils.go index a7d1cbb2d..d1e275645 100644 --- a/pkg/rlptools/wasm_utils.go +++ b/pkg/rlptools/wasm_utils.go @@ -5,124 +5,253 @@ import ( "errors" "fmt" "reflect" + "strings" _struct "github.com/golang/protobuf/ptypes/struct" istioclientgoextensionv1alpha1 "istio.io/client-go/pkg/apis/extensions/v1alpha1" "sigs.k8s.io/controller-runtime/pkg/client" gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "github.com/kuadrant/kuadrant-operator/pkg/common" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" ) var ( WASMFilterImageURL = common.FetchEnv("RELATED_IMAGE_WASMSHIM", "oci://quay.io/kuadrant/wasm-shim:latest") ) -type GatewayAction struct { - Configurations []kuadrantv1beta1.Configuration `json:"configurations"` +// WasmRules computes WASM rules from the policy and the targeted route. +// It returns an empty list of wasm rules if the policy specifies no limits or if all limits specified in the policy +// fail to match any route rule according to the limits route selectors. +func WasmRules(rlp *kuadrantv1beta2.RateLimitPolicy, route *gatewayapiv1beta1.HTTPRoute) []wasm.Rule { + rules := make([]wasm.Rule, 0) + if rlp == nil { + return rules + } + + for limitName := range rlp.Spec.Limits { + // 1 RLP limit <---> 1 WASM rule + limit := rlp.Spec.Limits[limitName] + limitIdentifier := LimitNameToLimitadorIdentifier(limitName) + rule, err := ruleFromLimit(limitIdentifier, &limit, route) + if err == nil { + rules = append(rules, rule) + } + } - // +optional - Rules []kuadrantv1beta1.Rule `json:"rules,omitempty"` + return rules } -func DefaultGatewayConfiguration(key client.ObjectKey) []kuadrantv1beta1.Configuration { - return []kuadrantv1beta1.Configuration{ - { - Actions: []kuadrantv1beta1.ActionSpecifier{ - { - GenericKey: &kuadrantv1beta1.GenericKeySpec{ - DescriptorValue: key.String(), - // using default value as specified in Envoy spec - // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#config-route-v3-ratelimit-action-generickey - DescriptorKey: &[]string{"ratelimitpolicy"}[0], - }, - }, - }, - }, +func ruleFromLimit(limitIdentifier string, limit *kuadrantv1beta2.Limit, route *gatewayapiv1beta1.HTTPRoute) (wasm.Rule, error) { + rule := wasm.Rule{} + + conditions, err := conditionsFromLimit(limit, route) + if err != nil { + return rule, err + } + + rule.Conditions = conditions + + if data := dataFromLimt(limitIdentifier, limit); data != nil { + rule.Data = data } + + return rule, nil } -// GatewayActionsFromRateLimitPolicy return flatten list from GatewayAction from the RLP -func GatewayActionsFromRateLimitPolicy(rlp *kuadrantv1beta1.RateLimitPolicy, route *gatewayapiv1beta1.HTTPRoute) []GatewayAction { - flattenActions := make([]GatewayAction, 0) - if rlp == nil { - return flattenActions +func conditionsFromLimit(limit *kuadrantv1beta2.Limit, route *gatewayapiv1beta1.HTTPRoute) ([]wasm.Condition, error) { + if limit == nil { + return nil, errors.New("limit should not be nil") } - for idx := range rlp.Spec.RateLimits { - // Skip those RateLimit objects with empty configurations, even if they have rules defined - if len(rlp.Spec.RateLimits[idx].Configurations) == 0 { - continue + routeConditions := make([]wasm.Condition, 0) + + if len(limit.RouteSelectors) > 0 { + // build conditions from the rules selected by the route selectors + for idx := range limit.RouteSelectors { + routeSelector := limit.RouteSelectors[idx] + hostnamesForConditions := hostnamesForConditions(route, &routeSelector) + for _, rule := range routeSelector.SelectRules(route) { + routeConditions = append(routeConditions, conditionsFromRule(rule, hostnamesForConditions)...) + } } - // if HTTPRoute is available, fill empty rules with defaults from the route - rules := rlp.Spec.RateLimits[idx].Rules - if route != nil && len(rules) == 0 { - rules = HTTPRouteRulesToRLPRules(common.RulesFromHTTPRoute(route)) + if len(routeConditions) == 0 { + return nil, errors.New("cannot match any route rules, check for invalid route selectors in the policy") } + } else { + // build conditions from all rules if no route selectors are defined + for _, rule := range route.Spec.Rules { + routeConditions = append(routeConditions, conditionsFromRule(rule, hostnamesForConditions(route, nil))...) + } + } - flattenActions = append(flattenActions, GatewayAction{ - Configurations: rlp.Spec.RateLimits[idx].Configurations, - Rules: rules, - }) + if len(limit.When) == 0 { + if len(routeConditions) == 0 { + return nil, nil + } + return routeConditions, nil } - if len(rlp.Spec.RateLimits) > 0 && len(flattenActions) == 0 { - // no configurations specified in the rlp, - // then apply the default configuration (action list) and default rules from the route - flattenActions = []GatewayAction{ - { - Configurations: DefaultGatewayConfiguration(client.ObjectKeyFromObject(rlp)), - Rules: HTTPRouteRulesToRLPRules(common.RulesFromHTTPRoute(route)), - }, + if len(routeConditions) > 0 { + // merge the 'when' conditions into each route level one + mergedConditions := make([]wasm.Condition, len(routeConditions)) + for _, when := range limit.When { + for idx := range routeConditions { + mergedCondition := routeConditions[idx] + mergedCondition.AllOf = append(mergedCondition.AllOf, patternExpresionFromWhen(when)) + mergedConditions[idx] = mergedCondition + } } + return mergedConditions, nil } - return flattenActions + // build conditions only from the 'when' field + whenConditions := make([]wasm.Condition, len(limit.When)) + for idx, when := range limit.When { + whenConditions[idx] = wasm.Condition{AllOf: []wasm.PatternExpression{patternExpresionFromWhen(when)}} + } + return whenConditions, nil } -func HTTPRouteRulesToRLPRules(httpRouteRules []common.HTTPRouteRule) []kuadrantv1beta1.Rule { - rlpRules := make([]kuadrantv1beta1.Rule, 0, len(httpRouteRules)) - for idx := range httpRouteRules { - var tmp []string - rlpRules = append(rlpRules, kuadrantv1beta1.Rule{ - // copy slice - Paths: append(tmp, httpRouteRules[idx].Paths...), - Methods: append(tmp, httpRouteRules[idx].Methods...), - Hosts: append(tmp, httpRouteRules[idx].Hosts...), - }) - } - return rlpRules +// hostnamesForConditions allows avoiding building conditions for hostnames that are excluded by the selector +// or when the hostname is irrelevant (i.e. matches all hostnames) +func hostnamesForConditions(route *gatewayapiv1beta1.HTTPRoute, routeSelector *kuadrantv1beta2.RouteSelector) []gatewayapiv1beta1.Hostname { + hostnames := route.Spec.Hostnames + + if routeSelector != nil && len(routeSelector.Hostnames) > 0 { + hostnames = common.Intersection(routeSelector.Hostnames, hostnames) + } + + if common.SameElements(hostnames, route.Spec.Hostnames) { + return []gatewayapiv1beta1.Hostname{"*"} + } + + return hostnames } -type RateLimitPolicy struct { - Name string `json:"name"` - RateLimitDomain string `json:"rate_limit_domain"` - UpstreamCluster string `json:"upstream_cluster"` - Hostnames []string `json:"hostnames"` - // +optional - GatewayActions []GatewayAction `json:"gateway_actions,omitempty"` +// conditionsFromRule builds a list of conditions from a rule and a list of hostnames +// each combination of a rule match and hostname yields one condition +// rules that specify no explicit match are assumed to match all request (i.e. implicit catch-all rule) +// empty list of hostnames yields a condition without a hostname pattern expression +func conditionsFromRule(rule gatewayapiv1beta1.HTTPRouteRule, hostnames []gatewayapiv1beta1.Hostname) (conditions []wasm.Condition) { + if len(rule.Matches) == 0 { + for _, hostname := range hostnames { + if hostname == "*" { + continue + } + condition := wasm.Condition{AllOf: []wasm.PatternExpression{patternExpresionFromHostname(hostname)}} + conditions = append(conditions, condition) + } + return + } + + for _, match := range rule.Matches { + condition := wasm.Condition{AllOf: patternExpresionsFromMatch(match)} + + if len(hostnames) > 0 { + for _, hostname := range hostnames { + if hostname == "*" { + conditions = append(conditions, condition) + continue + } + mergedCondition := condition + mergedCondition.AllOf = append(mergedCondition.AllOf, patternExpresionFromHostname(hostname)) + conditions = append(conditions, mergedCondition) + } + continue + } + + conditions = append(conditions, condition) + } + return } -type WASMPlugin struct { - FailureModeDeny bool `json:"failure_mode_deny"` - RateLimitPolicies []RateLimitPolicy `json:"rate_limit_policies"` +func patternExpresionsFromMatch(match gatewayapiv1beta1.HTTPRouteMatch) []wasm.PatternExpression { + expressions := make([]wasm.PatternExpression, 0) + + if match.Path != nil { + expressions = append(expressions, patternExpresionFromPathMatch(*match.Path)) + } + + if match.Method != nil { + expressions = append(expressions, patternExpresionFromMethod(*match.Method)) + } + + // TODO(eastizle): only paths and methods implemented + + return expressions } -func (w *WASMPlugin) ToStruct() (*_struct.Struct, error) { - wasmPluginJSON, err := json.Marshal(w) - if err != nil { - return nil, err +func patternExpresionFromPathMatch(pathMatch gatewayapiv1beta1.HTTPPathMatch) wasm.PatternExpression { + var ( + operator = wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator) // default value + value = "/" // default value + ) + + if pathMatch.Value != nil { + value = *pathMatch.Value } - pluginConfigStruct := &_struct.Struct{} - if err := pluginConfigStruct.UnmarshalJSON(wasmPluginJSON); err != nil { - return nil, err + if pathMatch.Type != nil { + if val, ok := wasm.PathMatchTypeMap[*pathMatch.Type]; ok { + operator = val + } + } + + return wasm.PatternExpression{ + Selector: "request.url_path", + Operator: operator, + Value: value, } - return pluginConfigStruct, nil } -func WASMPluginFromStruct(structure *_struct.Struct) (*WASMPlugin, error) { +func patternExpresionFromMethod(method gatewayapiv1beta1.HTTPMethod) wasm.PatternExpression { + return wasm.PatternExpression{ + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), + Value: string(method), + } +} + +func patternExpresionFromHostname(hostname gatewayapiv1beta1.Hostname) wasm.PatternExpression { + value := string(hostname) + operator := "eq" + if strings.HasPrefix(value, "*.") { + operator = "endswith" + value = value[1:] + } + return wasm.PatternExpression{ + Selector: "request.host", + Operator: wasm.PatternOperator(operator), + Value: value, + } +} + +func patternExpresionFromWhen(when kuadrantv1beta2.WhenCondition) wasm.PatternExpression { + return wasm.PatternExpression{ + Selector: when.Selector, + Operator: wasm.PatternOperator(when.Operator), + Value: when.Value, + } +} + +func dataFromLimt(limitIdentifier string, limit *kuadrantv1beta2.Limit) (data []wasm.DataItem) { + if limit == nil { + return + } + + // static key representing the limit + data = append(data, wasm.DataItem{Static: &wasm.StaticSpec{Key: limitIdentifier, Value: "1"}}) + + for _, counter := range limit.Counters { + data = append(data, wasm.DataItem{Selector: &wasm.SelectorSpec{Selector: counter}}) + } + + return data +} + +func WASMPluginFromStruct(structure *_struct.Struct) (*wasm.Plugin, error) { if structure == nil { return nil, errors.New("cannot desestructure WASMPlugin from nil") } @@ -132,7 +261,7 @@ func WASMPluginFromStruct(structure *_struct.Struct) (*WASMPlugin, error) { return nil, err } // Deserialize struct into PluginConfig struct - wasmPlugin := &WASMPlugin{} + wasmPlugin := &wasm.Plugin{} if err := json.Unmarshal(configJSON, wasmPlugin); err != nil { return nil, err } @@ -140,12 +269,7 @@ func WASMPluginFromStruct(structure *_struct.Struct) (*WASMPlugin, error) { return wasmPlugin, nil } -type GatewayActionsByDomain map[string][]GatewayAction - -func (g GatewayActionsByDomain) String() string { - jsonData, _ := json.MarshalIndent(g, "", " ") - return string(jsonData) -} +type WasmRulesByDomain map[string][]wasm.Rule func WASMPluginMutator(existingObj, desiredObj client.Object) (bool, error) { update := false diff --git a/pkg/rlptools/wasm_utils_test.go b/pkg/rlptools/wasm_utils_test.go index 6766d4688..9771679d3 100644 --- a/pkg/rlptools/wasm_utils_test.go +++ b/pkg/rlptools/wasm_utils_test.go @@ -3,56 +3,25 @@ package rlptools import ( - "reflect" "testing" - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" - "github.com/kuadrant/kuadrant-operator/pkg/common" - "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) -func TestHTTPRouteRulesToRLPRules(t *testing.T) { - testCases := []struct { - name string - routeRules []common.HTTPRouteRule - expectedRLPRules []kuadrantv1beta1.Rule - }{ - { - "nil rules", nil, make([]kuadrantv1beta1.Rule, 0), - }, - { - "rule with paths methods and hosts", - []common.HTTPRouteRule{ - { - Hosts: []string{"*", "*.example.com"}, - Paths: []string{"/admin/*", "/cats"}, - Methods: []string{"GET", "POST"}, - }, - }, []kuadrantv1beta1.Rule{ - { - Hosts: []string{"*", "*.example.com"}, - Paths: []string{"/admin/*", "/cats"}, - Methods: []string{"GET", "POST"}, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(subT *testing.T) { - rules := HTTPRouteRulesToRLPRules(tc.routeRules) - if !reflect.DeepEqual(rules, tc.expectedRLPRules) { - subT.Errorf("expected rules (%+v), got (%+v)", tc.expectedRLPRules, rules) - } - }) - } -} + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" +) -func TestGatewayActionsFromRateLimitPolicy(t *testing.T) { +// TODO(eastizle): missing WASMPluginMutator tests +// TODO(eastizle): missing TestWasmRules use cases tests. Only happy path +func TestWasmRules(t *testing.T) { httpRoute := &gatewayapiv1beta1.HTTPRoute{ Spec: gatewayapiv1beta1.HTTPRouteSpec{ - Hostnames: []gatewayapiv1beta1.Hostname{"*.example.com"}, + Hostnames: []gatewayapiv1beta1.Hostname{ + "*.example.com", + "*.apps.example.internal", + }, Rules: []gatewayapiv1beta1.HTTPRouteRule{ { Matches: []gatewayapiv1beta1.HTTPRouteMatch{ @@ -69,171 +38,300 @@ func TestGatewayActionsFromRateLimitPolicy(t *testing.T) { }, } - t.Run("empty rate limits return empty actions", func(subT *testing.T) { - rlp := &kuadrantv1beta1.RateLimitPolicy{ - Spec: kuadrantv1beta1.RateLimitPolicySpec{ - RateLimits: []kuadrantv1beta1.RateLimit{}, + catchAllHTTPRoute := &gatewayapiv1beta1.HTTPRoute{ + Spec: gatewayapiv1beta1.HTTPRouteSpec{ + Hostnames: []gatewayapiv1beta1.Hostname{"*"}, + }, + } + + rlp := func(name string, limits map[string]kuadrantv1beta2.Limit) *kuadrantv1beta2.RateLimitPolicy { + return &kuadrantv1beta2.RateLimitPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "my-app", + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + Limits: limits, }, } - expectedGatewayActions := []GatewayAction{} + } - gatewayActions := GatewayActionsFromRateLimitPolicy(rlp, httpRoute) - if !reflect.DeepEqual(gatewayActions, expectedGatewayActions) { - t.Errorf("expected gw actions (%+v), got (%+v)", expectedGatewayActions, gatewayActions) - } - }) + // a simple 50rps counter, for convinience, to be used in tests + counter50rps := kuadrantv1beta2.Rate{ + Limit: 50, + Duration: 1, + Unit: kuadrantv1beta2.TimeUnit("second"), + } - t.Run("basic test", func(subT *testing.T) { - rlp := &kuadrantv1beta1.RateLimitPolicy{ - Spec: kuadrantv1beta1.RateLimitPolicySpec{ - RateLimits: []kuadrantv1beta1.RateLimit{ - { - Configurations: defaultConfigurations(), - Rules: []kuadrantv1beta1.Rule{ - { - Hosts: []string{"*.protected.example.com"}, + testCases := []struct { + name string + rlp *kuadrantv1beta2.RateLimitPolicy + route *gatewayapiv1beta1.HTTPRoute + expectedRules []wasm.Rule + }{ + { + name: "minimal RLP", + rlp: rlp("minimal", map[string]kuadrantv1beta2.Limit{ + "50rps": { + Rates: []kuadrantv1beta2.Rate{counter50rps}, + }, + }), + route: httpRoute, + expectedRules: []wasm.Rule{ + { + Conditions: []wasm.Condition{ + { + AllOf: []wasm.PatternExpression{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), + Value: "GET", + }, }, }, }, - { - Configurations: defaultConfigurations(), - Rules: nil, + Data: []wasm.DataItem{ + { + Static: &wasm.StaticSpec{ + Key: "limit.50rps__770adfd9", + Value: "1", + }, + }, }, }, }, - } - - expectedGatewayActions := []GatewayAction{ - { - Configurations: defaultConfigurations(), - Rules: []kuadrantv1beta1.Rule{ - { - Hosts: []string{"*.protected.example.com"}, + }, + { + name: "RLP with route selector based on hostname", + rlp: rlp("my-rlp", map[string]kuadrantv1beta2.Limit{ + "50rps-for-selected-hostnames": { + Rates: []kuadrantv1beta2.Rate{counter50rps}, + RouteSelectors: []kuadrantv1beta2.RouteSelector{ + { + Hostnames: []gatewayapiv1beta1.Hostname{ + "*.example.com", + "myapp.apps.example.com", // ignored + }, + }, }, }, - }, - { - Configurations: defaultConfigurations(), - Rules: []kuadrantv1beta1.Rule{ - { - Hosts: []string{"*.example.com"}, - Paths: []string{"/toy*"}, - Methods: []string{"GET"}, + }), + route: httpRoute, + expectedRules: []wasm.Rule{ + { + Conditions: []wasm.Condition{ + { + AllOf: []wasm.PatternExpression{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), + Value: "GET", + }, + { + Selector: "request.host", + Operator: wasm.PatternOperator(kuadrantv1beta2.EndsWithOperator), + Value: ".example.com", + }, + }, + }, + }, + Data: []wasm.DataItem{ + { + Static: &wasm.StaticSpec{ + Key: "limit.50rps_for_selected_hostnames__5af2c820", + Value: "1", + }, + }, }, }, }, - } - gatewayActions := GatewayActionsFromRateLimitPolicy(rlp, httpRoute) - if !reflect.DeepEqual(gatewayActions, expectedGatewayActions) { - t.Errorf("expected gw actions (%+v), got (%+v)", expectedGatewayActions, gatewayActions) - } - }) - - t.Run("when the configuration obj is missing skip it", func(subT *testing.T) { - rlp := &kuadrantv1beta1.RateLimitPolicy{ - Spec: kuadrantv1beta1.RateLimitPolicySpec{ - RateLimits: []kuadrantv1beta1.RateLimit{ - { - // configurations object is missing - Rules: []kuadrantv1beta1.Rule{{Hosts: []string{"a.example.com"}}}, - }, - { - Configurations: defaultConfigurations(), - Rules: []kuadrantv1beta1.Rule{{Hosts: []string{"b.example.com"}}}, + }, + { + name: "RLP with route selector based on http route matches (full match)", + rlp: rlp("my-rlp", map[string]kuadrantv1beta2.Limit{ + "50rps-for-selected-route": { + Rates: []kuadrantv1beta2.Rate{counter50rps}, + RouteSelectors: []kuadrantv1beta2.RouteSelector{ + { + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchPathPrefix}[0], + Value: &[]string{"/toy"}[0], + }, + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethod("GET")}[0], + }, + }, + }, }, }, - }, - } - - expectedGatewayActions := []GatewayAction{ - { - Configurations: defaultConfigurations(), - Rules: []kuadrantv1beta1.Rule{{Hosts: []string{"b.example.com"}}}, - }, - } - - gatewayActions := GatewayActionsFromRateLimitPolicy(rlp, httpRoute) - if !reflect.DeepEqual(gatewayActions, expectedGatewayActions) { - t.Errorf("expected gw actions (%+v), got (%+v)", expectedGatewayActions, gatewayActions) - } - }) - - t.Run("when rlp targeting a httproute does not have any configuration obj then default is applied", func(subT *testing.T) { - rlp := &kuadrantv1beta1.RateLimitPolicy{ - Spec: kuadrantv1beta1.RateLimitPolicySpec{ - RateLimits: []kuadrantv1beta1.RateLimit{ - { - // configurations object is missing - Rules: []kuadrantv1beta1.Rule{{Hosts: []string{"a.example.com"}}}, + }), + route: httpRoute, + expectedRules: []wasm.Rule{ + { + Conditions: []wasm.Condition{ + { + AllOf: []wasm.PatternExpression{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), + Value: "GET", + }, + }, + }, }, - { - // configurations object is missing - Rules: []kuadrantv1beta1.Rule{{Hosts: []string{"b.example.com"}}}, + Data: []wasm.DataItem{ + { + Static: &wasm.StaticSpec{ + Key: "limit.50rps_for_selected_route__b6640119", + Value: "1", + }, + }, }, }, }, - } - - expectedGatewayActions := []GatewayAction{ - { - Configurations: DefaultGatewayConfiguration(client.ObjectKeyFromObject(rlp)), - Rules: []kuadrantv1beta1.Rule{ - { - Hosts: []string{"*.example.com"}, - Paths: []string{"/toy*"}, - Methods: []string{"GET"}, + }, + { + name: "RLP with route selector based on http route matches (partial match)", + rlp: rlp("my-rlp", map[string]kuadrantv1beta2.Limit{ + "50rps-for-selected-path": { + Rates: []kuadrantv1beta2.Rate{counter50rps}, + RouteSelectors: []kuadrantv1beta2.RouteSelector{ + { + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchPathPrefix}[0], + Value: &[]string{"/toy"}[0], + }, + }, + }, + }, }, }, - }, - } - - gatewayActions := GatewayActionsFromRateLimitPolicy(rlp, httpRoute) - if !reflect.DeepEqual(gatewayActions, expectedGatewayActions) { - t.Errorf("expected gw actions (%+v), got (%+v)", expectedGatewayActions, gatewayActions) - } - }) - - t.Run("when rlp targeting a gateway does not have any configuration obj then default is applied", func(subT *testing.T) { - rlp := &kuadrantv1beta1.RateLimitPolicy{ - Spec: kuadrantv1beta1.RateLimitPolicySpec{ - RateLimits: []kuadrantv1beta1.RateLimit{ - { - // configurations object is missing - Rules: []kuadrantv1beta1.Rule{{Hosts: []string{"a.example.com"}}}, + }), + route: httpRoute, + expectedRules: []wasm.Rule{ + { + Conditions: []wasm.Condition{ + { + AllOf: []wasm.PatternExpression{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), + Value: "GET", + }, + }, + }, }, - { - // configurations object is missing - Rules: []kuadrantv1beta1.Rule{{Hosts: []string{"b.example.com"}}}, + Data: []wasm.DataItem{ + { + Static: &wasm.StaticSpec{ + Key: "limit.50rps_for_selected_path__4088dcf9", + Value: "1", + }, + }, }, }, }, - } - - expectedGatewayActions := []GatewayAction{ - { - Configurations: DefaultGatewayConfiguration(client.ObjectKeyFromObject(rlp)), - Rules: []kuadrantv1beta1.Rule{}, + }, + { + name: "RLP with mismatching route selectors", + rlp: rlp("my-rlp", map[string]kuadrantv1beta2.Limit{ + "50rps-for-non-existent-route": { + Rates: []kuadrantv1beta2.Rate{counter50rps}, + RouteSelectors: []kuadrantv1beta2.RouteSelector{ + { + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Method: &[]gatewayapiv1beta1.HTTPMethod{gatewayapiv1beta1.HTTPMethod("POST")}[0], + }, + }, + }, + }, + }, + }), + route: httpRoute, + expectedRules: []wasm.Rule{}, + }, + { + name: "HTTPRouteRules without rule matches", + rlp: rlp("my-rlp", map[string]kuadrantv1beta2.Limit{ + "50rps": { + Rates: []kuadrantv1beta2.Rate{counter50rps}, + }, + }), + route: catchAllHTTPRoute, + expectedRules: []wasm.Rule{ + { + Conditions: nil, + Data: []wasm.DataItem{ + { + Static: &wasm.StaticSpec{ + Key: "limit.50rps__770adfd9", + Value: "1", + }, + }, + }, + }, }, - } - - gatewayActions := GatewayActionsFromRateLimitPolicy(rlp, nil) - if !reflect.DeepEqual(gatewayActions, expectedGatewayActions) { - t.Errorf("expected gw actions (%+v), got (%+v)", expectedGatewayActions, gatewayActions) - } - }) -} - -func defaultConfigurations() []kuadrantv1beta1.Configuration { - return []kuadrantv1beta1.Configuration{ + }, { - Actions: []kuadrantv1beta1.ActionSpecifier{ + name: "RLP with counter qualifier", + rlp: rlp("my-rlp", map[string]kuadrantv1beta2.Limit{ + "50rps-per-username": { + Rates: []kuadrantv1beta2.Rate{counter50rps}, + Counters: []kuadrantv1beta2.ContextSelector{"auth.identity.username"}, + }, + }), + route: catchAllHTTPRoute, + expectedRules: []wasm.Rule{ { - GenericKey: &kuadrantv1beta1.GenericKeySpec{ - DescriptorValue: "some value", - DescriptorKey: &[]string{"some key"}[0], + Conditions: nil, + Data: []wasm.DataItem{ + { + Static: &wasm.StaticSpec{ + Key: "limit.50rps_per_username__f5bebfb8", + Value: "1", + }, + }, + { + Selector: &wasm.SelectorSpec{ + Selector: "auth.identity.username", + }, + }, }, }, }, }, } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + computedRules := WasmRules(tc.rlp, tc.route) + if diff := cmp.Diff(tc.expectedRules, computedRules); diff != "" { + t.Errorf("unexpected wasm rules (-want +got):\n%s", diff) + } + }) + } }