From 87670c48af114b4f005b53fff3ab34abd9ad6de0 Mon Sep 17 00:00:00 2001 From: Yusan Kurban <21994267+yusank@users.noreply.github.com> Date: Mon, 19 Dec 2022 10:31:03 +0800 Subject: [PATCH] fix: token update callback func (#20) * clean: rollback pab Signed-off-by: Yusan Kurban --- .../v1alpha1/clustervalidatepolicy_types.go | 51 +- apis/policy/v1alpha1/zz_generated.deepcopy.go | 60 - ...kcloudlabs.io_clustervalidatepolicies.yaml | 229 +-- docs/apidoc.config.json | 28 - docs/crd-apidoc.md | 1768 ----------------- docs/template/members.tpl | 51 - docs/template/pkg.tpl | 49 - docs/template/type.tpl | 81 - go.mod | 1 + go.sum | 1 + utils/cue/params.go | 36 - utils/cue/params_test.go | 93 - utils/interrupter/base_interrupter.go | 5 + utils/interrupter/base_interrupter_test.go | 20 - .../clusteroverridepolicy_interrupter.go | 69 +- .../clustervalidatepolicy_interrupter.go | 130 +- utils/interrupter/model/validate_model.go | 63 - .../interrupter/model/validate_model_test.go | 78 +- .../interrupter/overridepolicy_interrupter.go | 118 +- .../overridepolicy_interrupter_test.go | 71 + utils/interrupter/policy_interrupter.go | 16 + utils/interrupter/policy_interrupter_test.go | 349 +--- .../templates/validate.go.tmpl | 60 - .../templates/validate.template.go | 60 - utils/tokenmanager/fake/tokengenerator.go | 4 + utils/tokenmanager/tokengenerator.go | 28 +- utils/tokenmanager/tokengenerator_test.go | 74 +- utils/tokenmanager/tokenmanager.go | 130 +- utils/tokenmanager/tokenmanager_test.go | 43 +- utils/validatemanager/validatemanager.go | 9 - utils/validatemanager/validatemanager_test.go | 22 - 31 files changed, 538 insertions(+), 3259 deletions(-) delete mode 100644 docs/apidoc.config.json delete mode 100644 docs/crd-apidoc.md delete mode 100644 docs/template/members.tpl delete mode 100644 docs/template/pkg.tpl delete mode 100644 docs/template/type.tpl create mode 100644 utils/interrupter/overridepolicy_interrupter_test.go diff --git a/apis/policy/v1alpha1/clustervalidatepolicy_types.go b/apis/policy/v1alpha1/clustervalidatepolicy_types.go index 18d2fc4..04e6295 100644 --- a/apis/policy/v1alpha1/clustervalidatepolicy_types.go +++ b/apis/policy/v1alpha1/clustervalidatepolicy_types.go @@ -49,7 +49,6 @@ type ValidateRuleWithOperation struct { // Template of condition which defines validate cond, and // it will be rendered to CUE and store in RenderedCue field, so // if there are any data added manually will be erased. - // Note: template and podAvailableBadge are **MUTUALLY EXCLUSIVE**, template is priority to take effect. // +optional Template *ValidateRuleTemplate `json:"template,omitempty"` @@ -62,26 +61,21 @@ type ValidateRuleWithOperation struct { // ValidateRuleTemplate defines template for validate rule type ValidateRuleTemplate struct { // Type represents current rule operate field type. - // +kubebuilder:validation:Enum=condition;pab + // +kubebuilder:validation:Enum=condition // +required Type ValidateRuleType `json:"type,omitempty"` // Condition represents general condition rule for more custom demand. // +optional Condition *ValidateCondition `json:"condition,omitempty"` - // PodAvailableBadge stores the number or percentage to make sure a group pod won't down to zero replica. - // +optional - PodAvailableBadge *PodAvailableBadge `json:"podAvailableBadge,omitempty"` } // ValidateRuleType is definition for type of single validate rule template -// +kubebuilder:validation:Enum=condition;pab +// +kubebuilder:validation:Enum=condition type ValidateRuleType string const ( // ValidateRuleTypeCondition - general rule type ValidateRuleTypeCondition = "condition" - // ValidateRuleTypePodAvailableBadge - rule of pod available badge - ValidateRuleTypePodAvailableBadge = "pab" // add more types here... ) @@ -183,47 +177,6 @@ const ( OperationTypeDivide = "/" ) -type PodAvailableBadge struct { - // MaxUnavailable sets the percentage or number of max unavailable pod of workload. - // e.g. if sets 60% and workload replicas is 5, then maxUnavailable is 3 - // maxUnavailable and minAvailable are mutually exclusive, maxUnavailable is priority to take effect. - // +optional - MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"` - // MinAvailable sets the percentage or number of min available pod of workload. - // e.g. if sets 40% and workload replicas is 5, then minAvailable is 2. - // +optional - MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty"` - // ReplicaReference represents owner of current pod, in default case no need to set this field since - // in most of the cases we can get replicas of owner workload. But in some cases, pod might run without - // owner workload, so it need to get replicas from some other resource or remote api. - // +optional - ReplicaReference *ReplicaResourceRefer `json:"replicaReference,omitempty"` -} - -// ReplicaResourceRefer defines different types of replica ref data -type ReplicaResourceRefer struct { - // From represents where this referenced object are. - // +kubebuilder:validation:Enum=current;old;k8s;owner;http - // +required - From ValueRefFrom `json:"from,omitempty"` - // TargetReplicaPath represents the path of target replica field from k8s resource or http response. - // For k8s resource, usually put "/spec/replica" - // For http resource, put something like "body.data.targetReplica" - // +optional - TargetReplicaPath string `json:"targetReplicaPath,omitempty"` - // CurrentReplicaPath represents the path of current replica field from k8s resource or http response. - // For k8s resource, usually put "/status/replica" - // For http resource, put something like "body.data.currentReplica" - // +optional - CurrentReplicaPath string `json:"currentReplicaPath,omitempty"` - // K8s means refer another object from current cluster. - // +optional - K8s *ResourceSelector `json:"k8s,omitempty"` - // Http means refer data from remote api. - // +optional - Http *HttpDataRef `json:"http,omitempty"` -} - // +genclient // +genclient:nonNamespaced // +kubebuilder:resource:scope="Cluster",shortName=cvp diff --git a/apis/policy/v1alpha1/zz_generated.deepcopy.go b/apis/policy/v1alpha1/zz_generated.deepcopy.go index 7a528f8..f894271 100644 --- a/apis/policy/v1alpha1/zz_generated.deepcopy.go +++ b/apis/policy/v1alpha1/zz_generated.deepcopy.go @@ -464,61 +464,6 @@ func (in *PlaintextOverrider) DeepCopy() *PlaintextOverrider { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodAvailableBadge) DeepCopyInto(out *PodAvailableBadge) { - *out = *in - if in.MaxUnavailable != nil { - in, out := &in.MaxUnavailable, &out.MaxUnavailable - *out = new(intstr.IntOrString) - **out = **in - } - if in.MinAvailable != nil { - in, out := &in.MinAvailable, &out.MinAvailable - *out = new(intstr.IntOrString) - **out = **in - } - if in.ReplicaReference != nil { - in, out := &in.ReplicaReference, &out.ReplicaReference - *out = new(ReplicaResourceRefer) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodAvailableBadge. -func (in *PodAvailableBadge) DeepCopy() *PodAvailableBadge { - if in == nil { - return nil - } - out := new(PodAvailableBadge) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ReplicaResourceRefer) DeepCopyInto(out *ReplicaResourceRefer) { - *out = *in - if in.K8s != nil { - in, out := &in.K8s, &out.K8s - *out = new(ResourceSelector) - (*in).DeepCopyInto(*out) - } - if in.Http != nil { - in, out := &in.Http, &out.Http - *out = new(HttpDataRef) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaResourceRefer. -func (in *ReplicaResourceRefer) DeepCopy() *ReplicaResourceRefer { - if in == nil { - return nil - } - out := new(ReplicaResourceRefer) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceRefer) DeepCopyInto(out *ResourceRefer) { *out = *in @@ -643,11 +588,6 @@ func (in *ValidateRuleTemplate) DeepCopyInto(out *ValidateRuleTemplate) { *out = new(ValidateCondition) (*in).DeepCopyInto(*out) } - if in.PodAvailableBadge != nil { - in, out := &in.PodAvailableBadge, &out.PodAvailableBadge - *out = new(PodAvailableBadge) - (*in).DeepCopyInto(*out) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidateRuleTemplate. diff --git a/charts/_crds/bases/policy.kcloudlabs.io_clustervalidatepolicies.yaml b/charts/_crds/bases/policy.kcloudlabs.io_clustervalidatepolicies.yaml index e33dd75..2766338 100644 --- a/charts/_crds/bases/policy.kcloudlabs.io_clustervalidatepolicies.yaml +++ b/charts/_crds/bases/policy.kcloudlabs.io_clustervalidatepolicies.yaml @@ -139,11 +139,9 @@ spec: type: string type: array template: - description: 'Template of condition which defines validate cond, + description: Template of condition which defines validate cond, and it will be rendered to CUE and store in RenderedCue field, - so if there are any data added manually will be erased. Note: - template and podAvailableBadge are **MUTUALLY EXCLUSIVE**, - template is priority to take effect.' + so if there are any data added manually will be erased. properties: condition: description: Condition represents general condition rule @@ -645,235 +643,12 @@ spec: type: string type: object type: object - podAvailableBadge: - description: PodAvailableBadge stores the number or percentage - to make sure a group pod won't down to zero replica. - properties: - maxUnavailable: - anyOf: - - type: integer - - type: string - description: MaxUnavailable sets the percentage or number - of max unavailable pod of workload. e.g. if sets 60% - and workload replicas is 5, then maxUnavailable is - 3 maxUnavailable and minAvailable are mutually exclusive, - maxUnavailable is priority to take effect. - x-kubernetes-int-or-string: true - minAvailable: - anyOf: - - type: integer - - type: string - description: MinAvailable sets the percentage or number - of min available pod of workload. e.g. if sets 40% - and workload replicas is 5, then minAvailable is 2. - x-kubernetes-int-or-string: true - replicaReference: - description: ReplicaReference represents owner of current - pod, in default case no need to set this field since - in most of the cases we can get replicas of owner - workload. But in some cases, pod might run without - owner workload, so it need to get replicas from some - other resource or remote api. - properties: - currentReplicaPath: - description: CurrentReplicaPath represents the path - of current replica field from k8s resource or - http response. For k8s resource, usually put "/status/replica" - For http resource, put something like "body.data.currentReplica" - type: string - from: - allOf: - - enum: - - current - - old - - k8s - - owner - - http - - enum: - - current - - old - - k8s - - owner - - http - description: From represents where this referenced - object are. - type: string - http: - description: Http means refer data from remote api. - properties: - auth: - description: 'Auth defines basic info for get - authorization token before do request. Note: - it will request authURL with post and `Header.Set("Authorization", - "Basic "+basicAuth(username, password))` and - get token from response body. Response Body - must be a valid json and contains token like - this: `{"token": "xxx"} . After get the token, - the request will add a new key value to header, - key is "Authorization" and value is "Bearer - xxx".' - properties: - authUrl: - description: AuthURL represents remote url - to request and get token. - type: string - expireAt: - description: ExpireAt sores the token expire - time. Same as above field, this field - also updated automatically. This filed - is not fill by user, so don't edit it. - format: date-time - type: string - expireDuration: - description: ExpireDuration is providing - for some auth api won't return exact expire - time, so can you this field set an expiry - duration for token - type: string - password: - description: Password represents Password - for auth. - type: string - staticToken: - description: StaticToken represents for - static token for call api instead of get - token from remote api. StaticToken and - other fields are mutually exclusive, staticToken - is priority to take effect. - type: string - token: - description: Token stores the latest token - get from AuthURL, and it'll be updated - when token expired. This filed is not - fill by user, so don't edit it. - type: string - username: - description: Username represents username - for auth. - type: string - type: object - body: - description: Body represents the json body when - http method is POST. - x-kubernetes-preserve-unknown-fields: true - header: - additionalProperties: - type: string - description: Header represents the custom header - added to http request header. - type: object - method: - description: Method as basic http method(e.g. - GET or POST) - enum: - - GET - - POST - type: string - params: - additionalProperties: - type: string - description: Params represents the query value - for http request. - type: object - url: - description: URL as whole http url - type: string - type: object - k8s: - description: K8s means refer another object from - current cluster. - properties: - apiVersion: - description: APIVersion represents the API version - of the target resources. - type: string - kind: - description: Kind represents the Kind of the - target resources. - type: string - labelSelector: - description: A label query over a set of resources. - If name is not empty, labelSelector will be - ignored. - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - name: - description: Name of the target resource. Default - is empty, which means selecting all resources. - type: string - namespace: - description: Namespace of the target resource. - Default is empty, which means inherit from - the parent object scope. - type: string - required: - - apiVersion - - kind - type: object - targetReplicaPath: - description: TargetReplicaPath represents the path - of target replica field from k8s resource or http - response. For k8s resource, usually put "/spec/replica" - For http resource, put something like "body.data.targetReplica" - type: string - type: object - type: object type: allOf: - enum: - condition - - pab - enum: - condition - - pab description: Type represents current rule operate field type. type: string diff --git a/docs/apidoc.config.json b/docs/apidoc.config.json deleted file mode 100644 index f323323..0000000 --- a/docs/apidoc.config.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "hideMemberFields": [ - "TypeMeta" - ], - "hideTypePatterns": [ - "ParseError$", - "List$" - ], - "externalPackages": [ - { - "typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$", - "docsURLTemplate": "https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration" - }, - { - "typeMatchPrefix": "^k8s\\.io/(api|apimachinery/pkg/apis)/", - "docsURLTemplate": "https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#{{lower .TypeIdentifier}}-{{arrIndex .PackageSegments -1}}-{{arrIndex .PackageSegments -2}}" - }, - { - "typeMatchPrefix": "^github\\.com/knative/pkg/apis/duck/", - "docsURLTemplate": "https://pkg.go.dev/github.com/knative/pkg/apis/duck/{{arrIndex .PackageSegments -1}}#{{.TypeIdentifier}}" - } - ], - "typeDisplayNamePrefixOverrides": { - "k8s.io/api/": "Kubernetes ", - "k8s.io/apimachinery/pkg/apis/": "Kubernetes " - }, - "markdownDisabled": false -} diff --git a/docs/crd-apidoc.md b/docs/crd-apidoc.md deleted file mode 100644 index 0d06342..0000000 --- a/docs/crd-apidoc.md +++ /dev/null @@ -1,1768 +0,0 @@ -

Packages:

- -

policy.kcloudlabs.io/v1alpha1

-Resource Types: - -

ClusterOverridePolicy -

-
-

ClusterOverridePolicy represents the cluster-wide policy that overrides a group of resources.

-
- - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-apiVersion
-string
- -policy.kcloudlabs.io/v1alpha1 - -
-kind
-string -
ClusterOverridePolicy
-metadata
- - -Kubernetes meta/v1.ObjectMeta - - -
-Refer to the Kubernetes API documentation for the fields of the -metadata field. -
-spec
- - -OverridePolicySpec - - -
-

Spec represents the desired behavior of ClusterOverridePolicy.

-
-
- - - - - - - - - -
-resourceSelectors
- - -[]ResourceSelector - - -
-(Optional) -

ResourceSelectors restricts resource types that this override policy applies to. -nil means matching all resources.

-
-overrideRules
- - -[]RuleWithOperation - - -
-(Required) -

OverrideRules defines a collection of override rules on target operations.

-
-
-

ClusterValidatePolicy -

-
-

ClusterValidatePolicy represents the cluster-wide policy that validate a group of resources.

-
- - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-apiVersion
-string
- -policy.kcloudlabs.io/v1alpha1 - -
-kind
-string -
ClusterValidatePolicy
-metadata
- - -Kubernetes meta/v1.ObjectMeta - - -
-Refer to the Kubernetes API documentation for the fields of the -metadata field. -
-spec
- - -ClusterValidatePolicySpec - - -
-
-
- - - - - - - - - -
-resourceSelectors
- - -[]ResourceSelector - - -
-(Optional) -

ResourceSelectors restricts resource types that this validate policy applies to. -nil means matching all resources.

-
-validateRules
- - -[]ValidateRuleWithOperation - - -
-(Required) -

ValidateRules defines a collection of validate rules on target operations.

-
-
-

OverridePolicy -

-
-

OverridePolicy represents the policy that overrides a group of resources.

-
- - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-apiVersion
-string
- -policy.kcloudlabs.io/v1alpha1 - -
-kind
-string -
OverridePolicy
-metadata
- - -Kubernetes meta/v1.ObjectMeta - - -
-Refer to the Kubernetes API documentation for the fields of the -metadata field. -
-spec
- - -OverridePolicySpec - - -
-
-
- - - - - - - - - -
-resourceSelectors
- - -[]ResourceSelector - - -
-(Optional) -

ResourceSelectors restricts resource types that this override policy applies to. -nil means matching all resources.

-
-overrideRules
- - -[]RuleWithOperation - - -
-(Required) -

OverrideRules defines a collection of override rules on target operations.

-
-
-

AffectMode -(string alias)

-

-(Appears on:ValidateCondition) -

-
-

AffectMode is defining match affect

-
-

ClusterValidatePolicySpec -

-

-(Appears on:ClusterValidatePolicy) -

-
-

ClusterValidatePolicySpec defines the desired behavior of ClusterValidatePolicy.

-
- - - - - - - - - - - - - - - - - -
FieldDescription
-resourceSelectors
- - -[]ResourceSelector - - -
-(Optional) -

ResourceSelectors restricts resource types that this validate policy applies to. -nil means matching all resources.

-
-validateRules
- - -[]ValidateRuleWithOperation - - -
-(Required) -

ValidateRules defines a collection of validate rules on target operations.

-
-

Cond -(string alias)

-

-(Appears on:ValidateCondition) -

-
-

Cond is validation condition for validator

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ValueDescription

"Equal"

CondEqual - Equal

-

"Exist"

CondExist - Exist

-

"Gt"

CondGreater - Gt

-

"Gte"

CondGreaterOrEqual - Gte

-

"In"

CondIn - In

-

"Lt"

CondLesser - Lt

-

"Lte"

CondLesserOrEqual - Lte

-

"NotEqual"

CondNotEqual - NotEqual

-

"NotExist"

CondNotExist - NotExist

-

"NotIn"

CondNotIn - NotIn

-

"Regex"

CondRegex match regex. e.g. /^\d{1,}$/

-
-

ConstantValue -

-

-(Appears on:OverrideRuleTemplate, ValidateCondition) -

-
-

ConstantValue defines exact types. Only one of field can be set.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-string
- -string - -
-(Optional) -

String as a string

-
-integer
- -int64 - -
-(Optional) -

Integer as an integer(int64)

-
-float
- - -Float64 - - -
-(Optional) -

Float as float but use string to store, so please provide in comma (e.g. float: “1.2”)

-
-boolean
- -bool - -
-(Optional) -

Boolean only true or false can be recognized.

-
-stringSlice
- -[]string - -
-(Optional) -

StringSlice as a slice of string(e.g. [“a”,“b”])

-
-integerSlice
- -[]int64 - -
-(Optional) -

IntegerSlice as a slice of integer(int64) (e.g. [1,2,3])

-
-floatSlice
- - -[]Float64 - - -
-(Optional) -

FloatSlice as a slice of float but using string (e.g. [“1.2”, “2.3”])

-
-stringMap
- -map[string]string - -
-(Optional) -

StringMap as key-value set and both are string.

-
-

Float64 -(string alias)

-

-(Appears on:ConstantValue, ResourcesOversellRule) -

-
-

Float64 is alias for float64 as string

-
-

HttpDataRef -

-

-(Appears on:ReplicaResourceRefer, ResourceRefer) -

-
-

HttpDataRef defines a http request essential params

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-url
- -string - -
-(Required) -

URL as whole http url

-
-method
- -string - -
-(Required) -

Method as basic http method(e.g. GET or POST)

-
-header
- -map[string]string - -
-(Optional) -

Header represents the custom header added to http request header.

-
-params
- -map[string]string - -
-(Optional) -

Params represents the query value for http request.

-
-body
- -k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON - -
-

Body represents the json body when http method is POST.

-
-

OperationType -(string alias)

-

-(Appears on:ValueProcess) -

-
-

OperationType defines the type of processing value.

-
-

OverridePolicySpec -

-

-(Appears on:ClusterOverridePolicy, OverridePolicy) -

-
-

OverridePolicySpec defines the desired behavior of OverridePolicy.

-
- - - - - - - - - - - - - - - - - -
FieldDescription
-resourceSelectors
- - -[]ResourceSelector - - -
-(Optional) -

ResourceSelectors restricts resource types that this override policy applies to. -nil means matching all resources.

-
-overrideRules
- - -[]RuleWithOperation - - -
-(Required) -

OverrideRules defines a collection of override rules on target operations.

-
-

OverrideRuleTemplate -

-

-(Appears on:Overriders) -

-
-

OverrideRuleTemplate represents a single template of rule definition

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-type
- - -OverrideRuleType - - -
-(Required) -

Type represents current rule operate field type.

-
-operation
- - -OverriderOperator - - -
-(Required) -

Operation represents current operation type.

-
-path
- -string - -
-(Optional) -

Path is field path of current object(e.g. /spec/affinity) -If current type is annotations or labels, then only need to provide the key, no need whole path.

-
-value
- - -ConstantValue - - -
-(Optional) -

Value sets exact value for rule, like enum or numbers -Must set value when type is regex.

-
-valueRef
- - -ResourceRefer - - -
-(Optional) -

ValueRef represents for value reference from current or remote object. -Need specify the type of object and how to get it.

-
-resources
- - -Kubernetes core/v1.ResourceRequirements - - -
-(Optional) -

Resources valid only when the type is resources

-
-resourcesOversell
- - -ResourcesOversellRule - - -
-(Optional) -

ResourcesOversell valid only when the type is resourcesOversell

-
-tolerations
- - -[]Kubernetes core/v1.Toleration - - -
-(Optional) -

Tolerations valid only when the type is tolerations

-
-affinity
- - -Kubernetes core/v1.Affinity - - -
-(Optional) -

Affinity valid only when the type is affinity

-
-

OverrideRuleType -(string alias)

-

-(Appears on:OverrideRuleTemplate) -

-
-

OverrideRuleType is definition for type of single override rule template

-
- - - - - - - - - - - - - - - - - - - - -
ValueDescription

"affinity"

OverrideRuleTypeAffinity - affinity

-

"annotations"

OverrideRuleTypeAnnotations - annotations

-

"labels"

OverrideRuleTypeLabels - labels

-

"resources"

OverrideRuleTypeResources - resources

-

"resourcesOversell"

OverrideRuleTypeResourcesOversell - resourcesOversell

-

"tolerations"

OverrideRuleTypeTolerations - tolerations

-
-

OverriderOperator -(string alias)

-

-(Appears on:OverrideRuleTemplate, PlaintextOverrider) -

-
-

OverriderOperator is the set of operators that can be used in an overrider.

-
- - - - - - - - - - - - - - -
ValueDescription

"add"

OverriderOpAdd - “add” value to object

-

"remove"

OverriderOpRemove - remove field form object

-

"replace"

OverriderOpReplace - remove and add value(if specified path doesn’t exist, it will add directly)

-
-

Overriders -

-

-(Appears on:RuleWithOperation) -

-
-

Overriders offers various alternatives to represent the override rules.

-

If more than one alternative exist, they will be applied with following order: -- RenderCue -- Cue -- Plaintext

-
- - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-plaintext
- - -[]PlaintextOverrider - - -
-(Optional) -

Plaintext represents override rules defined with plaintext overriders.

-
-cue
- -string - -
-(Optional) -

Cue represents override rules defined with cue code.

-
-template
- - -OverrideRuleTemplate - - -
-(Optional) -

Template of rule which defines override rule, and -it will be rendered to CUE and store in RenderedCue field, so -if there are any data added manually will be erased.

-
-renderedCue
- -string - -
-(Optional) -

RenderedCue represents override rule defined by Template. -Don’t modify the value of this field, modify Rules instead of.

-
-

PlaintextOverrider -

-

-(Appears on:Overriders) -

-
-

PlaintextOverrider is a simple overrider that overrides target fields -according to path, operator and value.

-
- - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-path
- -string - -
-

Path indicates the path of target field

-
-op
- - -OverriderOperator - - -
-

Operator indicates the operation on target field. -Available operators are: add, update and remove.

-
-value
- -k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON - -
-(Optional) -

Value to be applied to target field. -Must be empty when operator is Remove.

-
-

PodAvailableBadge -

-

-(Appears on:ValidateRuleTemplate) -

-
-
- - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-maxUnavailable
- -k8s.io/apimachinery/pkg/util/intstr.IntOrString - -
-(Optional) -

MaxUnavailable sets the percentage or number of max unavailable pod of workload. -e.g. if sets 60% and workload replicas is 5, then maxUnavailable is 3 -maxUnavailable and minAvailable are mutually exclusive, maxUnavailable is priority to take effect.

-
-minAvailable
- -k8s.io/apimachinery/pkg/util/intstr.IntOrString - -
-(Optional) -

MinAvailable sets the percentage or number of min available pod of workload. -e.g. if sets 40% and workload replicas is 5, then minAvailable is 2.

-
-replicaReference
- - -ReplicaResourceRefer - - -
-(Optional) -

ReplicaReference represents owner of current pod, in default case no need to set this field since -in most of the cases we can get replicas of owner workload. But in some cases, pod might run without -owner workload, so it need to get replicas from some other resource or remote api.

-
-

ReplicaResourceRefer -

-

-(Appears on:PodAvailableBadge) -

-
-

ReplicaResourceRefer defines different types of replica ref data

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-from
- - -ValueRefFrom - - -
-(Required) -

From represents where this referenced object are.

-
-targetReplicaPath
- -string - -
-(Optional) -

TargetReplicaPath represents the path of target replica field from k8s resource or http response. -For k8s resource, usually put “/spec/replica” -For http resource, put something like “body.data.targetReplica”

-
-currentReplicaPath
- -string - -
-(Optional) -

CurrentReplicaPath represents the path of current replica field from k8s resource or http response. -For k8s resource, usually put “/status/replica” -For http resource, put something like “body.data.currentReplica”

-
-k8s
- - -ResourceSelector - - -
-(Optional) -

K8s means refer another object from current cluster.

-
-http
- - -HttpDataRef - - -
-(Optional) -

Http means refer data from remote api.

-
-

ResourceRefer -

-

-(Appears on:OverrideRuleTemplate, ValidateCondition) -

-
-

ResourceRefer defines different types of ref data

-
- - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-from
- - -ValueRefFrom - - -
-(Required) -

From represents where this referenced object are.

-
-path
- -string - -
-(Optional) -

Path has different meaning, it represents current object field path like “/spec/replica” when From equals “current” -and it also can be format like “data.result.x.y” when From equals “http”, it represents the path in http response -Only when From is owner(means refer current object owner), the path can be empty.

-
-k8s
- - -ResourceSelector - - -
-(Optional) -

K8s means refer another object from current cluster.

-
-http
- - -HttpDataRef - - -
-(Optional) -

Http means refer data from remote api.

-
-

ResourceSelector -

-

-(Appears on:ClusterValidatePolicySpec, OverridePolicySpec, ReplicaResourceRefer, ResourceRefer) -

-
-

ResourceSelector the resources will be selected.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-apiVersion
- -string - -
-(Required) -

APIVersion represents the API version of the target resources.

-
-kind
- -string - -
-(Required) -

Kind represents the Kind of the target resources.

-
-namespace
- -string - -
-(Optional) -

Namespace of the target resource. -Default is empty, which means inherit from the parent object scope.

-
-name
- -string - -
-(Optional) -

Name of the target resource. -Default is empty, which means selecting all resources.

-
-labelSelector
- - -Kubernetes meta/v1.LabelSelector - - -
-(Optional) -

A label query over a set of resources. -If name is not empty, labelSelector will be ignored.

-
-

ResourcesOversellRule -

-

-(Appears on:OverrideRuleTemplate) -

-
-

ResourcesOversellRule defines factor of resource oversell

-
- - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-cpuFactor
- - -Float64 - - -
-(Optional) -

CpuFactor factor of cup oversell, it is float number less than 1, the range of value is (0,1.0)

-
-memoryFactor
- - -Float64 - - -
-(Optional) -

MemoryFactor factor of cup oversell, it is float number less than 1, the range of value is (0,1.0)

-
-diskFactor
- - -Float64 - - -
-(Optional) -

DiskFactor factor of cup oversell, it is float number less than 1, the range of value is (0,1.0)

-
-

RuleWithOperation -

-

-(Appears on:OverridePolicySpec) -

-
-

RuleWithOperation defines the override rules on operations.

-
- - - - - - - - - - - - - - - - - -
FieldDescription
-targetOperations
- - -[]Kubernetes admission/v1.Operation - - -
-

TargetOperations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * -for all of those operations and any future admission operations that are added. -If ‘*’ is present, the length of the slice must be one. -Required.

-
-overriders
- - -Overriders - - -
-(Required) -

Overriders represents the override rules that would apply on resources

-
-

ValidateCondition -

-

-(Appears on:ValidateRuleTemplate) -

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-affectMode
- - -AffectMode - - -
-(Required) -

AffectMode represents the mode of policy hit affect, in default case(reject), webhook rejects the operation when -policy hit, otherwise it will allow the operation. -If mode is allow, only allow the operation when policy hit, otherwise reject them all.

-
-cond
- - -Cond - - -
-(Required) -

Cond represents type of condition (e.g. Equal, Exist)

-
-dataRef
- - -ResourceRefer - - -
-(Required) -

DataRef represents for data reference from current or remote object. -Need specify the type of object and how to get it.

-
-message
- -string - -
-(Required) -

Message specify reject message when policy hit.

-
-value
- - -ConstantValue - - -
-(Optional) -

Value sets exact value for rule, like enum or numbers

-
-valueRef
- - -ResourceRefer - - -
-(Optional) -

ValueRef represents for value reference from current or remote object. -Need specify the type of object and how to get it.

-
-valueProcess
- - -ValueProcess - - -
-(Optional) -

ValueProcess represents handle process for value or valueRef. -Currently only support for number value, so make sure value or value from remote is a number.

-
-

ValidateRuleTemplate -

-

-(Appears on:ValidateRuleWithOperation) -

-
-

ValidateRuleTemplate defines template for validate rule

-
- - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-type
- - -ValidateRuleType - - -
-(Required) -

Type represents current rule operate field type.

-
-condition
- - -ValidateCondition - - -
-(Optional) -

Condition represents general condition rule for more custom demand.

-
-podAvailableBadge
- - -PodAvailableBadge - - -
-(Optional) -

PodAvailableBadge stores the number or percentage to make sure a group pod won’t down to zero replica.

-
-

ValidateRuleType -(string alias)

-

-(Appears on:ValidateRuleTemplate) -

-
-

ValidateRuleType is definition for type of single validate rule template

-
-

ValidateRuleWithOperation -

-

-(Appears on:ClusterValidatePolicySpec) -

-
-

ValidateRuleWithOperation defines validate rules on operations.

-
- - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-targetOperations
- - -[]Kubernetes admission/v1.Operation - - -
-

Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * -for all of those operations and any future admission operations that are added. -If ‘*’ is present, the length of the slice must be one. -Required.

-
-cue
- -string - -
-(Optional) -

Cue represents validate rules defined with cue code.

-
-template
- - -ValidateRuleTemplate - - -
-(Optional) -

Template of condition which defines validate cond, and -it will be rendered to CUE and store in RenderedCue field, so -if there are any data added manually will be erased. -Note: template and podAvailableBadge are MUTUALLY EXCLUSIVE, template is priority to take effect.

-
-renderedCue
- -string - -
-(Optional) -

RenderedCue represents validate rule defined by Template. -Don’t modify the value of this field, modify Rules instead of.

-
-

ValueProcess -

-

-(Appears on:ValidateCondition) -

-
-

ValueProcess defines operation to handle value to compare. -E.g. operation: ‘*’ -operationWith: 50% # or 0.5

-
- - - - - - - - - - - - - - - - - -
FieldDescription
-operation
- - -OperationType - - -
-(Required) -

Operation defines the type of operate value, and it should work with operationWith. -For example, operation is * and operationWith is 0.5 then in cue the value will be multiplied by 0.5.

-
-operationWith
- -k8s.io/apimachinery/pkg/util/intstr.IntOrString - -
-(Required) -

OperationWith defines value for operate to handle static value or value from remote.

-
-

ValueRefFrom -(string alias)

-

-(Appears on:ReplicaResourceRefer, ResourceRefer) -

-
-

ValueRefFrom defines where the override value comes from when value is refer other object or http response

-
- - - - - - - - - - - - - - - - -
ValueDescription

"current"

FromCurrentObject means read data from current k8s object(the newest one when update operate intercept)

-

"http"

FromHTTP - read data from http response

-

"k8s"

FromK8s - read data from other object in current kubernetes

-

"old"

FromOldObject means read data from old object, only used when object be updated

-
-

ValueType -(string alias)

-
-

ValueType defines whether value is specified by user or refer from other object

-
- - - - - - - - - - - - -
ValueDescription

"const"

ValueTypeConst means value is specified exactly.

-

"ref"

ValueTypeRefer means value is refer from other object

-
-
-

-Generated with gen-crd-api-reference-docs -on git commit 72fd287. -

diff --git a/docs/template/members.tpl b/docs/template/members.tpl deleted file mode 100644 index 9c11b92..0000000 --- a/docs/template/members.tpl +++ /dev/null @@ -1,51 +0,0 @@ -{{ define "members" }} - -{{ range .Members }} -{{ if not (hiddenMember .)}} - - - {{ fieldName . }}
- - {{ if linkForType .Type }} - - {{ typeDisplayName .Type }} - - {{ else }} - {{ typeDisplayName .Type }} - {{ end }} - - - - {{ if fieldEmbedded . }} -

- (Members of {{ fieldName . }} are embedded into this type.) -

- {{ end}} - - {{ if isOptionalMember .}} - (Optional) - {{ end }} - {{ if isRequiredMember .}} - (Required) - {{ end }} - - {{ safe (renderComments .CommentLines) }} - - {{ if and (eq (.Type.Name.Name) "ObjectMeta") }} - Refer to the Kubernetes API documentation for the fields of the - metadata field. - {{ end }} - - {{ if or (eq (fieldName .) "spec") }} -
-
- - {{ template "members" .Type }} -
- {{ end }} - - -{{ end }} -{{ end }} - -{{ end }} diff --git a/docs/template/pkg.tpl b/docs/template/pkg.tpl deleted file mode 100644 index 842ec93..0000000 --- a/docs/template/pkg.tpl +++ /dev/null @@ -1,49 +0,0 @@ -{{ define "packages" }} - -{{ with .packages}} -

Packages:

- -{{ end}} - -{{ range .packages }} -

- {{- packageDisplayName . -}} -

- - {{ with (index .GoPackages 0 )}} - {{ with .DocComments }} -
- {{ safe (renderComments .) }} -
- {{ end }} - {{ end }} - - Resource Types: - - - {{ range (visibleTypes (sortedTypes .Types))}} - {{ template "type" . }} - {{ end }} -
-{{ end }} - -

- Generated with gen-crd-api-reference-docs - {{ with .gitCommit }} on git commit {{ . }}{{end}}. -

- -{{ end }} diff --git a/docs/template/type.tpl b/docs/template/type.tpl deleted file mode 100644 index 8f0d66b..0000000 --- a/docs/template/type.tpl +++ /dev/null @@ -1,81 +0,0 @@ -{{ define "type" }} - -

- {{- .Name.Name }} - {{ if eq .Kind "Alias" }}({{.Underlying}} alias){{ end -}} -

-{{ with (typeReferences .) }} -

- (Appears on: - {{- $prev := "" -}} - {{- range . -}} - {{- if $prev -}}, {{ end -}} - {{- $prev = . -}} - {{ typeDisplayName . }} - {{- end -}} - ) -

-{{ end }} - -
- {{ safe (renderComments .CommentLines) }} -
- -{{ with (constantsOfType .) }} - - - - - - - - - {{- range . -}} - - {{- /* - renderComments implicitly creates a

element, so we - add one to the display name as well to make the contents - of the two cells align evenly. - */ -}} -

- - - {{- end -}} - -
ValueDescription

{{ typeDisplayName . }}

{{ safe (renderComments .CommentLines) }}
-{{ end }} - -{{ if .Members }} - - - - - - - - - {{ if isExportedType . }} - - - - - - - - - {{ end }} - {{ template "members" .}} - -
FieldDescription
- apiVersion
- string
- - {{apiGroup .}} - -
- kind
- string -
{{.Name.Name}}
-{{ end }} - -{{ end }} diff --git a/go.mod b/go.mod index 9bddd9d..c29ba14 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.11.0 github.com/stretchr/testify v1.7.0 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b gomodules.xyz/jsonpatch/v2 v2.2.0 k8s.io/api v0.23.6 diff --git a/go.sum b/go.sum index 9863d9b..d43c40b 100644 --- a/go.sum +++ b/go.sum @@ -625,6 +625,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ 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 h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= diff --git a/utils/cue/params.go b/utils/cue/params.go index f1f8197..3a37ea8 100644 --- a/utils/cue/params.go +++ b/utils/cue/params.go @@ -68,8 +68,6 @@ func BuildCueParamsViaValidatePolicy(c dynamiclister.DynamicResourceLister, curO switch tmpl.Type { case policyv1alpha1.ValidateRuleTypeCondition: return buildCueParamsForValidateCondition(c, curObject, tmpl.Condition) - case policyv1alpha1.ValidateRuleTypePodAvailableBadge: - return buildCueParamsForPAB(c, curObject, tmpl.PodAvailableBadge) default: return nil, fmt.Errorf("unknown template type(%v)", tmpl.Type) } @@ -134,40 +132,6 @@ func buildCueParamsForValidateCondition(c dynamiclister.DynamicResourceLister, c return cp, nil } -func buildCueParamsForPAB(c dynamiclister.DynamicResourceLister, curObject *unstructured.Unstructured, pab *policyv1alpha1.PodAvailableBadge) (*CueParams, error) { - var cp = &CueParams{ - ExtraParams: make(map[string]any), - } - - if pab.ReplicaReference == nil || pab.ReplicaReference.From == policyv1alpha1.FromOwnerReference { - // get owner reference in default case - obj, err := getOwnerReference(c, curObject) - if err != nil { - return nil, fmt.Errorf("getOwnerReference got error=%w", err) - } - cp.ExtraParams["otherObject"] = obj - return cp, nil - } - - if pab.ReplicaReference.From == policyv1alpha1.FromK8s { - obj, err := getObject(c, curObject, pab.ReplicaReference.K8s) - if err != nil { - return nil, fmt.Errorf("getObject got error=%w", err) - } - cp.ExtraParams["otherObject"] = obj - } - - if pab.ReplicaReference.From == policyv1alpha1.FromHTTP { - obj, err := getHttpResponse(nil, curObject, pab.ReplicaReference.Http) - if err != nil { - return nil, fmt.Errorf("getHttpResponse got error=%w", err) - } - cp.ExtraParams["http"] = obj - } - - return cp, nil -} - func getObject(c dynamiclister.DynamicResourceLister, obj *unstructured.Unstructured, rs *policyv1alpha1.ResourceSelector) (*unstructured.Unstructured, error) { gvk := schema.FromAPIVersionAndKind(rs.APIVersion, rs.Kind) diff --git a/utils/cue/params_test.go b/utils/cue/params_test.go index 7ebd525..9a0d2dc 100644 --- a/utils/cue/params_test.go +++ b/utils/cue/params_test.go @@ -9,7 +9,6 @@ import ( appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/util/intstr" policyv1alpha1 "github.com/k-cloud-labs/pkg/apis/policy/v1alpha1" "github.com/k-cloud-labs/pkg/utils/dynamiclister" @@ -865,98 +864,6 @@ func TestBuildCueParamsViaValidatePolicy(t *testing.T) { }, wantErr: false, }, - { - name: "pab_owner", - args: args{ - c: dc, - curObject: pod, - condition: &policyv1alpha1.ValidateRuleTemplate{ - Type: policyv1alpha1.ValidateRuleTypePodAvailableBadge, - PodAvailableBadge: &policyv1alpha1.PodAvailableBadge{ - MaxUnavailable: &intstr.IntOrString{ - Type: intstr.String, - StrVal: "60%", - }, - ReplicaReference: &policyv1alpha1.ReplicaResourceRefer{ - From: policyv1alpha1.FromOwnerReference, - }, - }, - }, - }, - want: &CueParams{ - ExtraParams: map[string]any{ - "otherObject": deployObj, - }, - }, - wantErr: false, - }, - { - name: "pab_k8s", - args: args{ - c: dc, - curObject: pod, - condition: &policyv1alpha1.ValidateRuleTemplate{ - Type: policyv1alpha1.ValidateRuleTypePodAvailableBadge, - PodAvailableBadge: &policyv1alpha1.PodAvailableBadge{ - MaxUnavailable: &intstr.IntOrString{ - Type: intstr.String, - StrVal: "60%", - }, - ReplicaReference: &policyv1alpha1.ReplicaResourceRefer{ - From: policyv1alpha1.FromK8s, - K8s: &policyv1alpha1.ResourceSelector{ - APIVersion: "apps/v1", - Kind: "Deployment", - Namespace: "{{metadata.namespace}}", - Name: "{{metadata.annotations.deploy-name}}", - }, - }, - }, - }, - }, - want: &CueParams{ - ExtraParams: map[string]any{ - "otherObject": deployObj, - }, - }, - wantErr: false, - }, - { - name: "pab_http", - args: args{ - c: dc, - curObject: pod, - condition: &policyv1alpha1.ValidateRuleTemplate{ - Type: policyv1alpha1.ValidateRuleTypePodAvailableBadge, - PodAvailableBadge: &policyv1alpha1.PodAvailableBadge{ - MaxUnavailable: &intstr.IntOrString{ - Type: intstr.String, - StrVal: "60%", - }, - ReplicaReference: &policyv1alpha1.ReplicaResourceRefer{ - From: policyv1alpha1.FromHTTP, - Http: &policyv1alpha1.HttpDataRef{ - URL: "http://127.0.0.1:8090/api/v1/token", - Method: "GET", - Params: map[string]string{ - "val": "{{metadata.name}}", - }, - }, - }, - }, - }, - }, - want: &CueParams{ - ExtraParams: map[string]any{ - "http": map[string]any{ - "body": map[string]any{ - "token": "pod", - }, - }, - }, - }, - wantErr: false, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/utils/interrupter/base_interrupter.go b/utils/interrupter/base_interrupter.go index 8203581..538d6b8 100644 --- a/utils/interrupter/base_interrupter.go +++ b/utils/interrupter/base_interrupter.go @@ -33,6 +33,11 @@ func (i *baseInterrupter) OnValidating(obj, oldObj *unstructured.Unstructured, o return nil } +func (i *baseInterrupter) OnStartUp() error { + // do nothing, need override this method + return nil +} + func NewBaseInterrupter(otm, vtm templatemanager.TemplateManager, cm templatemanager.CueManager) PolicyInterrupter { return &baseInterrupter{ overrideTemplateManager: otm, diff --git a/utils/interrupter/base_interrupter_test.go b/utils/interrupter/base_interrupter_test.go index f8ebd68..459b7db 100644 --- a/utils/interrupter/base_interrupter_test.go +++ b/utils/interrupter/base_interrupter_test.go @@ -79,26 +79,6 @@ func Test_baseInterrupter_renderAndFormat(t *testing.T) { }, wantErr: false, }, - { - name: "validatePAB", - args: args{ - data: &policyv1alpha1.ValidateRuleTemplate{ - Type: policyv1alpha1.ValidateRuleTypePodAvailableBadge, - PodAvailableBadge: &policyv1alpha1.PodAvailableBadge{ - MaxUnavailable: &intstr.IntOrString{ - Type: intstr.String, - StrVal: "60%", - }, - ReplicaReference: &policyv1alpha1.ReplicaResourceRefer{ - From: policyv1alpha1.FromOwnerReference, - TargetReplicaPath: "/spec/replica", - CurrentReplicaPath: "/status/replica", - }, - }, - }, - }, - wantErr: false, - }, { name: "overridePolicy", args: args{ diff --git a/utils/interrupter/clusteroverridepolicy_interrupter.go b/utils/interrupter/clusteroverridepolicy_interrupter.go index e4f7293..355ef5d 100644 --- a/utils/interrupter/clusteroverridepolicy_interrupter.go +++ b/utils/interrupter/clusteroverridepolicy_interrupter.go @@ -7,6 +7,7 @@ import ( jsonpatchv2 "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" policyv1alpha1 "github.com/k-cloud-labs/pkg/apis/policy/v1alpha1" @@ -45,14 +46,14 @@ func (c *clusterOverridePolicyInterrupter) OnMutating(obj, oldObj *unstructured. return nil, err } - if err = c.handleValueRef(cop, old, operation); err != nil { - return nil, err - } - + c.handleValueRef(cop, old, operation) return patches, nil } func (c *clusterOverridePolicyInterrupter) OnValidating(obj, oldObj *unstructured.Unstructured, operation admissionv1.Operation) error { + if operation == admissionv1.Delete { + return nil + } cop := new(policyv1alpha1.ClusterOverridePolicy) if err := convertToPolicy(obj, cop); err != nil { return err @@ -77,6 +78,19 @@ func (c *clusterOverridePolicyInterrupter) OnValidating(obj, oldObj *unstructure return c.validateOverridePolicy(&cop.Spec) } +func (c *clusterOverridePolicyInterrupter) OnStartUp() error { + list, err := c.lister.List(labels.Everything()) + if err != nil { + return err + } + + for _, policy := range list { + c.handleValueRef(policy, nil, admissionv1.Create) + } + + return nil +} + func NewClusterOverridePolicyInterrupter(opInterrupter PolicyInterrupter, lister v1alpha1.ClusterOverridePolicyLister) PolicyInterrupter { return &clusterOverridePolicyInterrupter{ overridePolicyInterrupter: opInterrupter.(*overridePolicyInterrupter), @@ -111,42 +125,42 @@ func (c *clusterOverridePolicyInterrupter) patchOverridePolicy(policy *policyv1a return patches, nil } -func (c *clusterOverridePolicyInterrupter) handleValueRef(policy, oldPolicy *policyv1alpha1.ClusterOverridePolicy, operation admissionv1.Operation) error { - newCallbackMap, err := c.getTokenCallbackMap(policy) - if err != nil { - return err - } +func (c *clusterOverridePolicyInterrupter) handleValueRef(policy, oldPolicy *policyv1alpha1.ClusterOverridePolicy, operation admissionv1.Operation) { + newCallbackMap := c.getTokenCallbackMap(policy) + var oldCallbackMap map[string]*tokenCallbackImpl if operation == admissionv1.Update && oldPolicy != nil { - oldCallbackMap, err := c.getTokenCallbackMap(oldPolicy) - if err != nil { - return err + oldCallbackMap = c.getTokenCallbackMap(oldPolicy) + } + + if operation == admissionv1.Create { + for _, impl := range newCallbackMap { + c.tokenManager.AddToken(impl.generator, impl) } + return + } - // remove old and add new - for _, impl := range oldCallbackMap { + if operation == admissionv1.Update { + needUpdate, needRemove := compareCallbackMap(newCallbackMap, oldCallbackMap) + for _, impl := range needRemove { c.tokenManager.RemoveToken(impl.generator, impl) } - } - if operation == admissionv1.Create || operation == admissionv1.Update { - for _, impl := range newCallbackMap { + for _, impl := range needUpdate { c.tokenManager.AddToken(impl.generator, impl) } - return nil + + return } if operation == admissionv1.Delete { for _, impl := range newCallbackMap { c.tokenManager.RemoveToken(impl.generator, impl) } - return nil } - - return nil } -func (c *clusterOverridePolicyInterrupter) getTokenCallbackMap(policy *policyv1alpha1.ClusterOverridePolicy) (map[string]*tokenCallbackImpl, error) { +func (c *clusterOverridePolicyInterrupter) getTokenCallbackMap(policy *policyv1alpha1.ClusterOverridePolicy) map[string]*tokenCallbackImpl { callbackMap := make(map[string]*tokenCallbackImpl) for i, overrideRule := range policy.Spec.OverrideRules { if overrideRule.Overriders.Template == nil { @@ -158,10 +172,7 @@ func (c *clusterOverridePolicyInterrupter) getTokenCallbackMap(policy *policyv1a continue } - tg, err := getTokenGeneratorFromRef(tmpl.ValueRef.Http) - if err != nil { - return nil, err - } + tg := getTokenGeneratorFromRef(tmpl.ValueRef.Http) if tg == nil { continue } @@ -174,8 +185,8 @@ func (c *clusterOverridePolicyInterrupter) getTokenCallbackMap(policy *policyv1a } } - cb.tokenPath = append(cb.tokenPath, fmt.Sprintf("/sepc/overrideRules/%d/overriders/template/valueRef/http/auth/token", i)) - cb.expirePath = append(cb.tokenPath, fmt.Sprintf("/sepc/overrideRules/%d/overriders/template/valueRef/http/auth/expireAt", i)) + cb.tokenPath = append(cb.tokenPath, fmt.Sprintf(opAuthPath, i, tokenKey)) + cb.expirePath = append(cb.tokenPath, fmt.Sprintf(opAuthPath, i, expireAtKey)) callbackMap[tg.ID()] = cb } @@ -184,7 +195,7 @@ func (c *clusterOverridePolicyInterrupter) getTokenCallbackMap(policy *policyv1a impl.callback = c.genCallback(impl, policy.Namespace, policy.Name) } - return callbackMap, nil + return callbackMap } func (c *clusterOverridePolicyInterrupter) getPolicy(_, name string) (client.Object, error) { diff --git a/utils/interrupter/clustervalidatepolicy_interrupter.go b/utils/interrupter/clustervalidatepolicy_interrupter.go index 192ec14..3785724 100644 --- a/utils/interrupter/clustervalidatepolicy_interrupter.go +++ b/utils/interrupter/clustervalidatepolicy_interrupter.go @@ -11,6 +11,7 @@ import ( admissionv1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" @@ -54,14 +55,15 @@ func (v *clusterValidatePolicyInterrupter) OnMutating(obj, oldObj *unstructured. return nil, err } - if err = v.handleValueRef(cvp, old, operation); err != nil { - return nil, err - } - + v.handleValueRef(cvp, old, operation) return patches, nil } func (v *clusterValidatePolicyInterrupter) OnValidating(obj, oldObj *unstructured.Unstructured, operation admissionv1.Operation) error { + if operation == admissionv1.Delete { + return nil + } + cvp := new(policyv1alpha1.ClusterValidatePolicy) if err := convertToPolicy(obj, cvp); err != nil { return err @@ -86,6 +88,19 @@ func (v *clusterValidatePolicyInterrupter) OnValidating(obj, oldObj *unstructure return v.validateClusterValidatePolicy(cvp) } +func (v *clusterValidatePolicyInterrupter) OnStartUp() error { + list, err := v.lister.List(labels.Everything()) + if err != nil { + return err + } + + for _, policy := range list { + v.handleValueRef(policy, nil, admissionv1.Create) + } + + return nil +} + func NewClusterValidatePolicyInterrupter(interrupter PolicyInterrupter, tm tokenmanager.TokenManager, client client.Client, lister v1alpha1.ClusterValidatePolicyLister) PolicyInterrupter { return &clusterValidatePolicyInterrupter{ @@ -128,20 +143,6 @@ func (v *clusterValidatePolicyInterrupter) patchClusterValidatePolicy(policy *po Value: policyv1alpha1.AffectModeReject, }) } - if validateRule.Template.PodAvailableBadge != nil && - validateRule.Template.PodAvailableBadge.ReplicaReference == nil { - validateRule.Template.PodAvailableBadge.ReplicaReference = &policyv1alpha1.ReplicaResourceRefer{ - From: policyv1alpha1.FromOwnerReference, - TargetReplicaPath: "/spec/replicas", - CurrentReplicaPath: "/status/replicas", - } - - patches = append(patches, jsonpatchv2.JsonPatchOperation{ - Operation: "replace", - Path: fmt.Sprintf("/spec/validateRules/%d/template/podAvailableBadge/replicaReference", i), - Value: validateRule.Template.PodAvailableBadge.ReplicaReference, - }) - } b, err := v.renderAndFormat(validateRule.Template) if err != nil { @@ -159,51 +160,56 @@ func (v *clusterValidatePolicyInterrupter) patchClusterValidatePolicy(policy *po return patches, nil } -func (v *clusterValidatePolicyInterrupter) handleValueRef(policy, oldPolicy *policyv1alpha1.ClusterValidatePolicy, operation admissionv1.Operation) error { - newCallbackMap, err := v.getTokenCallbackMap(policy) - if err != nil { - return err - } +func (v *clusterValidatePolicyInterrupter) handleValueRef(policy, oldPolicy *policyv1alpha1.ClusterValidatePolicy, operation admissionv1.Operation) { + newCallbackMap := v.getTokenCallbackMap(policy) + var oldCallbackMap map[string]*tokenCallbackImpl if operation == admissionv1.Update && oldPolicy != nil { - oldCallbackMap, err := v.getTokenCallbackMap(oldPolicy) - if err != nil { - return err + oldCallbackMap = v.getTokenCallbackMap(oldPolicy) + } + + if operation == admissionv1.Create { + for _, impl := range newCallbackMap { + v.tokenManager.AddToken(impl.generator, impl) } + return + } - // remove old and add new - for _, impl := range oldCallbackMap { + if operation == admissionv1.Update { + needUpdate, needRemove := compareCallbackMap(newCallbackMap, oldCallbackMap) + for _, impl := range needRemove { v.tokenManager.RemoveToken(impl.generator, impl) } - } - if operation == admissionv1.Create || operation == admissionv1.Update { - for _, impl := range newCallbackMap { + for _, impl := range needUpdate { v.tokenManager.AddToken(impl.generator, impl) } - return nil + + return } if operation == admissionv1.Delete { for _, impl := range newCallbackMap { v.tokenManager.RemoveToken(impl.generator, impl) } - return nil } - - return nil } -func (v *clusterValidatePolicyInterrupter) getTokenCallbackMap(policy *policyv1alpha1.ClusterValidatePolicy) ( - map[string]*tokenCallbackImpl, error) { +const ( + cvpTemplatePath = "/spec/validateRules/%d/template" + dataRefKey = "dataRef" + valueRefKey = "valueRef" + tokenKey = "token" + expireAtKey = "expireAt" + conditionAuthPath = cvpTemplatePath + "/condition/%s/http/auth/%s" +) + +func (v *clusterValidatePolicyInterrupter) getTokenCallbackMap(policy *policyv1alpha1.ClusterValidatePolicy) map[string]*tokenCallbackImpl { callbackMap := make(map[string]*tokenCallbackImpl) - checkAndAppend := func(ref *policyv1alpha1.HttpDataRef, tokenPath, expirePath string) error { - tg, err := getTokenGeneratorFromRef(ref) - if err != nil { - return err - } + checkAndAppend := func(ref *policyv1alpha1.HttpDataRef, tokenPath, expirePath string) { + tg := getTokenGeneratorFromRef(ref) if tg == nil { - return nil + return } cb, ok := callbackMap[tg.ID()] @@ -215,9 +221,8 @@ func (v *clusterValidatePolicyInterrupter) getTokenCallbackMap(policy *policyv1a } cb.tokenPath = append(cb.tokenPath, tokenPath) - cb.expirePath = append(cb.tokenPath, expirePath) + cb.expirePath = append(cb.expirePath, expirePath) callbackMap[tg.ID()] = cb - return nil } for i, rule := range policy.Spec.ValidateRules { @@ -229,35 +234,17 @@ func (v *clusterValidatePolicyInterrupter) getTokenCallbackMap(policy *policyv1a // condition if tmpl.Condition != nil { if tmpl.Condition.DataRef != nil && tmpl.Condition.DataRef.Http != nil { - err := checkAndAppend(tmpl.Condition.DataRef.Http, - fmt.Sprintf("/sepc/validateRules/%d/template/condition/dataRef/http/auth/token", i), - fmt.Sprintf("/sepc/validateRules/%d/template/condition/dataRef/http/auth/expireAt", i), + checkAndAppend(tmpl.Condition.DataRef.Http, + fmt.Sprintf(conditionAuthPath, i, dataRefKey, tokenKey), + fmt.Sprintf(conditionAuthPath, i, dataRefKey, expireAtKey), ) - if err != nil { - return nil, err - } } if tmpl.Condition.ValueRef != nil && tmpl.Condition.ValueRef.Http != nil { - err := checkAndAppend(tmpl.Condition.DataRef.Http, - fmt.Sprintf("/sepc/validateRules/%d/template/condition/valueRef/http/auth/token", i), - fmt.Sprintf("/sepc/validateRules/%d/template/condition/valueRef/http/auth/expireAt", i), + checkAndAppend(tmpl.Condition.DataRef.Http, + fmt.Sprintf(conditionAuthPath, i, valueRefKey, tokenKey), + fmt.Sprintf(conditionAuthPath, i, valueRefKey, expireAtKey), ) - if err != nil { - return nil, err - } - } - } - - // pab - if tmpl.PodAvailableBadge != nil && tmpl.PodAvailableBadge.ReplicaReference != nil && - tmpl.PodAvailableBadge.ReplicaReference.Http != nil { - err := checkAndAppend(tmpl.PodAvailableBadge.ReplicaReference.Http, - fmt.Sprintf("/sepc/validateRules/%d/template/AvailableBadge/replicaReference/http/auth/token", i), - fmt.Sprintf("/sepc/validateRules/%d/template/AvailableBadge/replicaReference/http/auth/expireAt", i), - ) - if err != nil { - return nil, err } } } @@ -267,7 +254,7 @@ func (v *clusterValidatePolicyInterrupter) getTokenCallbackMap(policy *policyv1a impl.callback = v.genCallback(impl, policy.Namespace, policy.Name) } - return callbackMap, nil + return callbackMap } func (v *clusterValidatePolicyInterrupter) getPolicy(_, name string) (client.Object, error) { @@ -308,6 +295,7 @@ func (v *clusterValidatePolicyInterrupter) genCallback(impl *tokenCallbackImpl, return err } - return v.client.Status().Patch(context.Background(), obj, client.RawPatch(types.JSONPatchType, patchBytes)) + klog.V(4).InfoS("before patch cvp", "cvp", obj.GetName(), "patchBytes", string(patchBytes)) + return v.client.Patch(context.Background(), obj, client.RawPatch(types.JSONPatchType, patchBytes)) } } diff --git a/utils/interrupter/model/validate_model.go b/utils/interrupter/model/validate_model.go index cbbd0ba..29adcde 100644 --- a/utils/interrupter/model/validate_model.go +++ b/utils/interrupter/model/validate_model.go @@ -14,7 +14,6 @@ import ( type ValidatePolicyRenderData struct { Type string Condition *ValidateCondition - PAB *PodAvailableBadge } type ValidateCondition struct { @@ -27,13 +26,6 @@ type ValidateCondition struct { Message string } -type PodAvailableBadge struct { - IsPercentage bool - MaxUnavailable *float64 - MinAvailable *float64 - ReplicaReference *ReplicaResourceRefer -} - func (vrd *ValidatePolicyRenderData) String() string { buf := &bytes.Buffer{} enc := json.NewEncoder(buf) @@ -53,14 +45,6 @@ type ResourceRefer struct { Path string } -type ReplicaResourceRefer struct { - From policyv1alpha1.ValueRefFrom - // will convert to cue reference - CueObjectKey string - TargetReplicaPath string - CurrentReplicaPath string -} - type ValueProcess struct { Operation policyv1alpha1.OperationType OperationWith float64 @@ -70,7 +54,6 @@ func ValidateRulesToValidatePolicyRenderData(vc *policyv1alpha1.ValidateRuleTemp return &ValidatePolicyRenderData{ Type: string(vc.Type), Condition: convertGeneralCondition(vc.Condition), - PAB: convertPAB(vc.PodAvailableBadge), } } @@ -126,28 +109,6 @@ func convertGeneralCondition(vc *policyv1alpha1.ValidateCondition) *ValidateCond return nvc } -func convertPAB(pp *policyv1alpha1.PodAvailableBadge) *PodAvailableBadge { - if pp == nil { - return nil - } - var ( - mu *float64 - ma *float64 - isPercentage bool - ) - mu, isPercentage = intStr2FloatPtr(pp.MaxUnavailable) - if mu == nil { - ma, isPercentage = intStr2FloatPtr(pp.MinAvailable) - } - - return &PodAvailableBadge{ - IsPercentage: isPercentage, - MaxUnavailable: mu, - MinAvailable: ma, - ReplicaReference: convertReplicaResourceRefer("", pp.ReplicaReference), - } -} - func intStr2FloatPtr(is *intstr.IntOrString) (*float64, bool) { if is == nil { return nil, false @@ -175,30 +136,6 @@ func intStr2FloatPtr(is *intstr.IntOrString) (*float64, bool) { } -func convertReplicaResourceRefer(suffix string, rf *policyv1alpha1.ReplicaResourceRefer) *ReplicaResourceRefer { - if rf == nil { - return nil - } - nrf := &ReplicaResourceRefer{ - From: rf.From, - TargetReplicaPath: handlePath(rf.TargetReplicaPath), - CurrentReplicaPath: handlePath(rf.CurrentReplicaPath), - } - - switch rf.From { - case policyv1alpha1.FromCurrentObject: - nrf.CueObjectKey = "object" - case policyv1alpha1.FromOldObject: - nrf.CueObjectKey = "oldObject" - case policyv1alpha1.FromK8s, policyv1alpha1.FromOwnerReference: - nrf.CueObjectKey = "otherObject" + suffix - case policyv1alpha1.FromHTTP: - nrf.CueObjectKey = "http" + suffix - } - - return nrf -} - func convertResourceRefer(suffix string, rf *policyv1alpha1.ResourceRefer) *ResourceRefer { if rf == nil { return nil diff --git a/utils/interrupter/model/validate_model_test.go b/utils/interrupter/model/validate_model_test.go index c47a194..e902d1f 100644 --- a/utils/interrupter/model/validate_model_test.go +++ b/utils/interrupter/model/validate_model_test.go @@ -14,7 +14,6 @@ func TestValidatePolicyRenderData_String(t *testing.T) { type fields struct { Type string Condition *ValidateCondition - PAB *PodAvailableBadge } tests := []struct { name string @@ -49,8 +48,7 @@ func TestValidatePolicyRenderData_String(t *testing.T) { }, "ValueProcess": null, "Message": "no pass" - }, - "PAB": null + } } `, }, @@ -60,7 +58,6 @@ func TestValidatePolicyRenderData_String(t *testing.T) { vrd := &ValidatePolicyRenderData{ Type: tt.fields.Type, Condition: tt.fields.Condition, - PAB: tt.fields.PAB, } if got := vrd.String(); got != tt.want { t.Errorf("String() = %v, want %v", got, tt.want) @@ -171,79 +168,6 @@ func Test_convertGeneralCondition(t *testing.T) { } } -func Test_convertPAB(t *testing.T) { - fp := func(f float64) *float64 { - return &f - } - type args struct { - pp *policyv1alpha1.PodAvailableBadge - } - tests := []struct { - name string - args args - want *PodAvailableBadge - }{ - { - name: "1", - args: args{ - pp: &policyv1alpha1.PodAvailableBadge{ - MaxUnavailable: &intstr.IntOrString{ - Type: intstr.String, - StrVal: "60%", - }, - }, - }, - want: &PodAvailableBadge{ - IsPercentage: true, - MaxUnavailable: fp(0.6), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := convertPAB(tt.args.pp); !reflect.DeepEqual(got, tt.want) { - t.Errorf("convertPAB() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_convertReplicaResourceRefer(t *testing.T) { - type args struct { - suffix string - rf *policyv1alpha1.ReplicaResourceRefer - } - tests := []struct { - name string - args args - want *ReplicaResourceRefer - }{ - { - name: "1", - args: args{ - rf: &policyv1alpha1.ReplicaResourceRefer{ - From: policyv1alpha1.FromCurrentObject, - TargetReplicaPath: "/spec/replica", - CurrentReplicaPath: "/status/replica", - }, - }, - want: &ReplicaResourceRefer{ - From: policyv1alpha1.FromCurrentObject, - CueObjectKey: "object", - TargetReplicaPath: "spec.replica", - CurrentReplicaPath: "status.replica", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := convertReplicaResourceRefer(tt.args.suffix, tt.args.rf); !reflect.DeepEqual(got, tt.want) { - t.Errorf("convertReplicaResourceRefer() = %v, want %v", got, tt.want) - } - }) - } -} - func Test_convertResourceRefer(t *testing.T) { type args struct { suffix string diff --git a/utils/interrupter/overridepolicy_interrupter.go b/utils/interrupter/overridepolicy_interrupter.go index 6b3d421..304e75b 100644 --- a/utils/interrupter/overridepolicy_interrupter.go +++ b/utils/interrupter/overridepolicy_interrupter.go @@ -11,6 +11,7 @@ import ( admissionv1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" @@ -54,14 +55,15 @@ func (o *overridePolicyInterrupter) OnMutating(obj, oldObj *unstructured.Unstruc return nil, err } - if err = o.handleValueRef(op, old, operation); err != nil { - return nil, err - } - + o.handleValueRef(op, old, operation) return patches, nil } func (o *overridePolicyInterrupter) OnValidating(obj, oldObj *unstructured.Unstructured, operation admissionv1.Operation) error { + if operation == admissionv1.Delete { + return nil + } + op := new(policyv1alpha1.OverridePolicy) if err := convertToPolicy(obj, op); err != nil { return err @@ -86,6 +88,19 @@ func (o *overridePolicyInterrupter) OnValidating(obj, oldObj *unstructured.Unstr return o.validateOverridePolicy(&op.Spec) } +func (o *overridePolicyInterrupter) OnStartUp() error { + list, err := o.lister.List(labels.Everything()) + if err != nil { + return err + } + + for _, policy := range list { + o.handleValueRef(policy, nil, admissionv1.Create) + } + + return nil +} + func NewOverridePolicyInterrupter(interrupter PolicyInterrupter, tm tokenmanager.TokenManager, client client.Client, lister v1alpha1.OverridePolicyLister) PolicyInterrupter { return &overridePolicyInterrupter{ baseInterrupter: interrupter.(*baseInterrupter), @@ -132,42 +147,47 @@ func (o *overridePolicyInterrupter) patchOverridePolicy(policy *policyv1alpha1.O return patches, nil } -func (o *overridePolicyInterrupter) handleValueRef(policy, oldPolicy *policyv1alpha1.OverridePolicy, operation admissionv1.Operation) error { - newCallbackMap, err := o.getTokenCallbackMap(policy) - if err != nil { - return err - } +func (o *overridePolicyInterrupter) handleValueRef(policy, oldPolicy *policyv1alpha1.OverridePolicy, operation admissionv1.Operation) { + newCallbackMap := o.getTokenCallbackMap(policy) + var oldCallbackMap map[string]*tokenCallbackImpl if operation == admissionv1.Update && oldPolicy != nil { - oldCallbackMap, err := o.getTokenCallbackMap(oldPolicy) - if err != nil { - return err + oldCallbackMap = o.getTokenCallbackMap(oldPolicy) + } + + if operation == admissionv1.Create { + for _, impl := range newCallbackMap { + o.tokenManager.AddToken(impl.generator, impl) } + return + } - // remove old and add new - for _, impl := range oldCallbackMap { + if operation == admissionv1.Update { + needUpdate, needRemove := compareCallbackMap(newCallbackMap, oldCallbackMap) + for _, impl := range needRemove { o.tokenManager.RemoveToken(impl.generator, impl) } - } - if operation == admissionv1.Create || operation == admissionv1.Update { - for _, impl := range newCallbackMap { + for _, impl := range needUpdate { o.tokenManager.AddToken(impl.generator, impl) } - return nil + + return } if operation == admissionv1.Delete { for _, impl := range newCallbackMap { o.tokenManager.RemoveToken(impl.generator, impl) } - return nil } - - return nil } -func (o *overridePolicyInterrupter) getTokenCallbackMap(policy *policyv1alpha1.OverridePolicy) (map[string]*tokenCallbackImpl, error) { +const ( + opTemplatePath = "/spec/overrideRules/%d/overriders/template" + opAuthPath = opTemplatePath + "/valueRef/http/auth/%s" +) + +func (o *overridePolicyInterrupter) getTokenCallbackMap(policy *policyv1alpha1.OverridePolicy) map[string]*tokenCallbackImpl { callbackMap := make(map[string]*tokenCallbackImpl) for i, overrideRule := range policy.Spec.OverrideRules { if overrideRule.Overriders.Template == nil { @@ -179,10 +199,7 @@ func (o *overridePolicyInterrupter) getTokenCallbackMap(policy *policyv1alpha1.O continue } - tg, err := getTokenGeneratorFromRef(tmpl.ValueRef.Http) - if err != nil { - return nil, err - } + tg := getTokenGeneratorFromRef(tmpl.ValueRef.Http) if tg == nil { continue } @@ -195,8 +212,8 @@ func (o *overridePolicyInterrupter) getTokenCallbackMap(policy *policyv1alpha1.O } } - cb.tokenPath = append(cb.tokenPath, fmt.Sprintf("/sepc/overrideRules/%d/overriders/template/valueRef/http/auth/token", i)) - cb.expirePath = append(cb.tokenPath, fmt.Sprintf("/sepc/overrideRules/%d/overriders/template/valueRef/http/auth/expireAt", i)) + cb.tokenPath = append(cb.tokenPath, fmt.Sprintf(opAuthPath, i, tokenKey)) + cb.expirePath = append(cb.expirePath, fmt.Sprintf(opAuthPath, i, expireAtKey)) callbackMap[tg.ID()] = cb } @@ -205,7 +222,7 @@ func (o *overridePolicyInterrupter) getTokenCallbackMap(policy *policyv1alpha1.O impl.callback = o.genCallback(impl, policy.Namespace, policy.Name) } - return callbackMap, nil + return callbackMap } func (o *overridePolicyInterrupter) getPolicy(namespace, name string) (client.Object, error) { @@ -246,21 +263,13 @@ func (o *overridePolicyInterrupter) genCallback(impl *tokenCallbackImpl, namespa return err } - return o.client.Status().Patch(context.Background(), obj, client.RawPatch(types.JSONPatchType, patchBytes)) + return o.client.Patch(context.Background(), obj, client.RawPatch(types.JSONPatchType, patchBytes)) } } -func getTokenGeneratorFromRef(ref *policyv1alpha1.HttpDataRef) (tokenmanager.TokenGenerator, error) { - if ref == nil { - return nil, nil - } - - if ref.Auth == nil || ref.Auth.StaticToken != "" { - return nil, nil - } - - if ref.Auth.AuthURL == "" { - return nil, nil +func getTokenGeneratorFromRef(ref *policyv1alpha1.HttpDataRef) tokenmanager.TokenGenerator { + if ref == nil || ref.Auth == nil || ref.Auth.StaticToken != "" || ref.Auth.AuthURL == "" { + return nil } return tokenmanager.NewTokenGenerator(ref.Auth.AuthURL, ref.Auth.Username, ref.Auth.Password, ref.Auth.ExpireDuration.Duration) @@ -282,3 +291,30 @@ func (t *tokenCallbackImpl) ID() string { func (t *tokenCallbackImpl) Callback(token string, expireAt time.Time) error { return t.callback(token, expireAt) } + +func compareCallbackMap(cur, old map[string]*tokenCallbackImpl) (update, remove map[string]*tokenCallbackImpl) { + update = make(map[string]*tokenCallbackImpl) + remove = make(map[string]*tokenCallbackImpl) + + for s, impl := range old { + if _, ok := cur[s]; !ok { + remove[s] = impl + } + } + + for s, impl := range cur { + if _, ok := old[s]; !ok { + update[s] = impl + continue + } + + // exist + oldImpl := old[s] + if !impl.generator.Equal(oldImpl.generator) { + remove[s] = oldImpl + update[s] = impl + } + } + + return +} diff --git a/utils/interrupter/overridepolicy_interrupter_test.go b/utils/interrupter/overridepolicy_interrupter_test.go new file mode 100644 index 0000000..607ff1b --- /dev/null +++ b/utils/interrupter/overridepolicy_interrupter_test.go @@ -0,0 +1,71 @@ +package interrupter + +import ( + "reflect" + "testing" + "time" + + "github.com/k-cloud-labs/pkg/utils/tokenmanager" +) + +func Test_compareCallbackMap(t *testing.T) { + type args struct { + cur map[string]*tokenCallbackImpl + old map[string]*tokenCallbackImpl + } + tests := []struct { + name string + args args + wantUpdate map[string]*tokenCallbackImpl + wantRemove map[string]*tokenCallbackImpl + }{ + { + name: "1", + args: args{ + cur: map[string]*tokenCallbackImpl{ + "1": { + id: "1", + generator: tokenmanager.NewTokenGenerator("1", "1", "1", time.Hour), + }, + "2": { + id: "2", + generator: tokenmanager.NewTokenGenerator("2", "1", "1", time.Hour), + }, + }, + old: map[string]*tokenCallbackImpl{ + "3": { + id: "3", + generator: tokenmanager.NewTokenGenerator("3", "1", "1", time.Hour), + }, + }, + }, + wantUpdate: map[string]*tokenCallbackImpl{ + "1": { + id: "1", + generator: tokenmanager.NewTokenGenerator("1", "1", "1", time.Hour), + }, + "2": { + id: "2", + generator: tokenmanager.NewTokenGenerator("2", "1", "1", time.Hour), + }, + }, + wantRemove: map[string]*tokenCallbackImpl{ + "3": { + id: "3", + generator: tokenmanager.NewTokenGenerator("3", "1", "1", time.Hour), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotUpdate, gotRemove := compareCallbackMap(tt.args.cur, tt.args.old) + if !reflect.DeepEqual(gotUpdate, tt.wantUpdate) { + t.Errorf("compareCallbackMap() gotUpdate = %v, want %v", gotUpdate, tt.wantUpdate) + } + if !reflect.DeepEqual(gotRemove, tt.wantRemove) { + t.Errorf("compareCallbackMap() gotRemove = %v, want %v", gotRemove, tt.wantRemove) + } + }) + } +} diff --git a/utils/interrupter/policy_interrupter.go b/utils/interrupter/policy_interrupter.go index a3d7951..e5463fa 100644 --- a/utils/interrupter/policy_interrupter.go +++ b/utils/interrupter/policy_interrupter.go @@ -1,11 +1,13 @@ package interrupter import ( + "context" "encoding/json" "strings" "sync" jsonpatch "github.com/evanphx/json-patch" + "golang.org/x/sync/errgroup" jsonpatchv2 "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -32,6 +34,9 @@ type PolicyInterrupter interface { // OnValidating called on "/validating" api to validate policy // return nil means obj is not defined policy or no invalid field OnValidating(obj, oldObj *unstructured.Unstructured, operation admissionv1.Operation) error + // OnStartUp called when webhook process initialize + // return error if initial phase get any error + OnStartUp() error } type policyInterrupterImpl struct { @@ -88,6 +93,17 @@ func (p *policyInterrupterImpl) getInterrupter(obj *unstructured.Unstructured) P return nil } +func (p *policyInterrupterImpl) OnStartUp() error { + eg, _ := errgroup.WithContext(context.Background()) + p.interrupters.Range(func(key, value any) bool { + interrupter := value.(PolicyInterrupter) + eg.Go(interrupter.OnStartUp) + return true + }) + + return eg.Wait() +} + func (p *policyInterrupterImpl) AddInterrupter(gvk schema.GroupVersionKind, pi PolicyInterrupter) { p.interrupters.Store(gvk, pi) } diff --git a/utils/interrupter/policy_interrupter_test.go b/utils/interrupter/policy_interrupter_test.go index e8a9d00..862b355 100644 --- a/utils/interrupter/policy_interrupter_test.go +++ b/utils/interrupter/policy_interrupter_test.go @@ -4,16 +4,19 @@ import ( "reflect" "testing" + "github.com/golang/mock/gomock" jsonpatchv2 "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" policyv1alpha1 "github.com/k-cloud-labs/pkg/apis/policy/v1alpha1" + "github.com/k-cloud-labs/pkg/test/mock" fakedtokenmanager "github.com/k-cloud-labs/pkg/utils/tokenmanager/fake" ) -func test_fakePolicyInterrupterManager() (PolicyInterrupterManager, error) { +func test_fakePolicyInterrupterManager(t *testing.T) (PolicyInterrupterManager, error) { // base baseInterrupter, err := test_baseInterrupter() if err != nil { @@ -23,8 +26,17 @@ func test_fakePolicyInterrupterManager() (PolicyInterrupterManager, error) { policyInterrupterManager := NewPolicyInterrupterManager() tokenManager := fakedtokenmanager.NewFakeTokenGenerator() + ctrl := gomock.NewController(t) + + opLister := mock.NewMockOverridePolicyLister(ctrl) + opLister.EXPECT().List(labels.Everything()).Return([]*policyv1alpha1.OverridePolicy{}, nil).AnyTimes() + copLister := mock.NewMockClusterOverridePolicyLister(ctrl) + copLister.EXPECT().List(labels.Everything()).Return([]*policyv1alpha1.ClusterOverridePolicy{}, nil).AnyTimes() + cvpLister := mock.NewMockClusterValidatePolicyLister(ctrl) + cvpLister.EXPECT().List(labels.Everything()).Return([]*policyv1alpha1.ClusterValidatePolicy{}, nil).AnyTimes() + // op - overridePolicyInterrupter := NewOverridePolicyInterrupter(baseInterrupter, tokenManager, nil, nil) + overridePolicyInterrupter := NewOverridePolicyInterrupter(baseInterrupter, tokenManager, nil, opLister) policyInterrupterManager.AddInterrupter(schema.GroupVersionKind{ Group: policyv1alpha1.SchemeGroupVersion.Group, Version: policyv1alpha1.SchemeGroupVersion.Version, @@ -35,19 +47,19 @@ func test_fakePolicyInterrupterManager() (PolicyInterrupterManager, error) { Group: policyv1alpha1.SchemeGroupVersion.Group, Version: policyv1alpha1.SchemeGroupVersion.Version, Kind: "ClusterOverridePolicy", - }, NewClusterOverridePolicyInterrupter(overridePolicyInterrupter, nil)) + }, NewClusterOverridePolicyInterrupter(overridePolicyInterrupter, copLister)) // cvp policyInterrupterManager.AddInterrupter(schema.GroupVersionKind{ Group: policyv1alpha1.SchemeGroupVersion.Group, Version: policyv1alpha1.SchemeGroupVersion.Version, Kind: "ClusterValidatePolicy", - }, NewClusterValidatePolicyInterrupter(baseInterrupter, tokenManager, nil, nil)) + }, NewClusterValidatePolicyInterrupter(baseInterrupter, tokenManager, nil, cvpLister)) return policyInterrupterManager, nil } func Test_policyInterrupterImpl_OnValidating(t *testing.T) { - policyInterrupter, err := test_fakePolicyInterrupterManager() + policyInterrupter, err := test_fakePolicyInterrupterManager(t) if err != nil { t.Error(err) return @@ -148,6 +160,18 @@ validate: { }, wantErr: false, }, + { + name: "1.3", + args: args{ + operation: admissionv1.Delete, + obj: &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "policy.kcloudlabs.io/v1alpha1", + "kind": "OverridePolicy", + }, + }, + }, + wantErr: false, + }, { name: "2", args: args{ @@ -221,6 +245,18 @@ validate: { }, wantErr: true, }, + { + name: "2.3", + args: args{ + operation: admissionv1.Delete, + obj: &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "policy.kcloudlabs.io/v1alpha1", + "kind": "ClusterOverridePolicy", + }, + }, + }, + wantErr: false, + }, { name: "3", args: args{ @@ -288,10 +324,22 @@ validate: { }, wantErr: true, }, + { + name: "3.3", + args: args{ + operation: admissionv1.Delete, + obj: &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "policy.kcloudlabs.io/v1alpha1", + "kind": "ClusterValidatePolicy", + }, + }, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := policyInterrupter.OnValidating(tt.args.obj, tt.args.oldObj, admissionv1.Create); (err != nil) != tt.wantErr { + if err := policyInterrupter.OnValidating(tt.args.obj, tt.args.oldObj, tt.args.operation); (err != nil) != tt.wantErr { t.Errorf("OnValidating() error = %v, wantErr %v", err, tt.wantErr) } }) @@ -299,7 +347,7 @@ validate: { } func Test_policyInterrupterImpl_OnMutating(t *testing.T) { - policyInterrupter, err := test_fakePolicyInterrupterManager() + policyInterrupter, err := test_fakePolicyInterrupterManager(t) if err != nil { t.Error(err) return @@ -1135,268 +1183,6 @@ validate: { } } } -`, - }, - }, - wantErr: false, - }, - { - name: "4", - args: args{ - operation: admissionv1.Create, - obj: &unstructured.Unstructured{Object: map[string]any{ - "apiVersion": "policy.kcloudlabs.io/v1alpha1", - "kind": "ClusterValidatePolicy", - "spec": map[string]any{ - "validateRules": []map[string]any{ - { - "template": map[string]any{ - "type": "pab", - "podAvailableBadge": map[string]any{ - "maxUnavailable": "60%", - }, - }, - }, - }, - }, - }}, - }, - want: []jsonpatchv2.JsonPatchOperation{ - { - Operation: "replace", - Path: "/spec/validateRules/0/template/podAvailableBadge/replicaReference", - Value: &policyv1alpha1.ReplicaResourceRefer{ - From: policyv1alpha1.FromOwnerReference, - TargetReplicaPath: "/spec/replicas", - CurrentReplicaPath: "/status/replicas", - }, - }, - { - Operation: "replace", - Path: "/spec/validateRules/0/renderedCue", - Value: `data: _ @tag(data) -object: data.object -oldObject: data.oldObject -otherObject: data.extraParams."otherObject" -validate: { - if otherObject.spec.replicas != _|_ { - if otherObject.status.replicas != _|_ { - // target - target * 0.6 > current - if otherObject.spec.replicas-otherObject.spec.replicas*0.6 > otherObject.status.replicas-1 { - { - valid: false - reason: "Cannot delete this pod, cause of hitting maxUnavailable(0.6)" - } - } - } - } -} -`, - }, - }, - wantErr: false, - }, - { - name: "4.1", - args: args{ - operation: admissionv1.Delete, - obj: &unstructured.Unstructured{Object: map[string]any{ - "apiVersion": "policy.kcloudlabs.io/v1alpha1", - "kind": "ClusterValidatePolicy", - "spec": map[string]any{ - "validateRules": []map[string]any{ - { - "template": map[string]any{ - "type": "pab", - "podAvailableBadge": map[string]any{ - "maxUnavailable": "60%", - }, - }, - }, - }, - }, - }}, - }, - want: nil, - wantErr: false, - }, - { - name: "5", - args: args{ - operation: admissionv1.Create, - obj: &unstructured.Unstructured{Object: map[string]any{ - "apiVersion": "policy.kcloudlabs.io/v1alpha1", - "kind": "ClusterValidatePolicy", - "spec": map[string]any{ - "validateRules": []map[string]any{ - { - "template": map[string]any{ - "type": "pab", - "podAvailableBadge": map[string]any{ - "maxUnavailable": "60%", - "replicaReference": map[string]any{ - "from": "http", - "targetReplicaPath": "body.target", - "currentReplicaPath": "body.origin", - "http": map[string]any{ - "url": "https://xxx.com", - "auth": map[string]any{ - "authUrl": "https://xxx.com", - "username": "xx", - "password": "xx", - }, - }, - }, - }, - }, - }, - }, - }, - }}, - }, - want: []jsonpatchv2.JsonPatchOperation{ - { - Operation: "replace", - Path: "/spec/validateRules/0/renderedCue", - Value: `data: _ @tag(data) -object: data.object -oldObject: data.oldObject -http: data.extraParams."http" -validate: { - if http.body.target != _|_ { - if http.body.origin != _|_ { - // target - target * 0.6 > current - if http.body.target-http.body.target*0.6 > http.body.origin-1 { - { - valid: false - reason: "Cannot delete this pod, cause of hitting maxUnavailable(0.6)" - } - } - } - } -} -`, - }, - }, - wantErr: false, - }, - { - name: "5.1", - args: args{ - operation: admissionv1.Delete, - obj: &unstructured.Unstructured{Object: map[string]any{ - "apiVersion": "policy.kcloudlabs.io/v1alpha1", - "kind": "ClusterValidatePolicy", - "spec": map[string]any{ - "validateRules": []map[string]any{ - { - "template": map[string]any{ - "type": "pab", - "podAvailableBadge": map[string]any{ - "maxUnavailable": "60%", - "replicaReference": map[string]any{ - "from": "http", - "targetReplicaPath": "body.target", - "currentReplicaPath": "body.origin", - "http": map[string]any{ - "url": "https://xxx.com", - "auth": map[string]any{ - "authUrl": "https://xxx.com", - "username": "xx", - "password": "xx", - }, - }, - }, - }, - }, - }, - }, - }, - }}, - }, - want: nil, - wantErr: false, - }, - { - name: "5.2", - args: args{ - operation: admissionv1.Update, - oldObj: &unstructured.Unstructured{Object: map[string]any{ - "apiVersion": "policy.kcloudlabs.io/v1alpha1", - "kind": "ClusterValidatePolicy", - "spec": map[string]any{ - "validateRules": []map[string]any{ - { - "template": map[string]any{ - "type": "pab", - "podAvailableBadge": map[string]any{ - "maxUnavailable": "60%", - "replicaReference": map[string]any{ - "from": "http", - "targetReplicaPath": "body.target", - "currentReplicaPath": "body.origin", - "http": map[string]any{ - "url": "https://xxx.com", - "auth": map[string]any{ - "authUrl": "https://xxx.com", - "username": "xx", - "password": "xx", - }, - }, - }, - }, - }, - }, - }, - }, - }}, - obj: &unstructured.Unstructured{Object: map[string]any{ - "apiVersion": "policy.kcloudlabs.io/v1alpha1", - "kind": "ClusterValidatePolicy", - "spec": map[string]any{ - "validateRules": []map[string]any{ - { - "template": map[string]any{ - "type": "pab", - "podAvailableBadge": map[string]any{ - "maxUnavailable": "60%", - }, - }, - }, - }, - }, - }}, - }, - want: []jsonpatchv2.JsonPatchOperation{ - { - Operation: "replace", - Path: "/spec/validateRules/0/template/podAvailableBadge/replicaReference", - Value: &policyv1alpha1.ReplicaResourceRefer{ - From: policyv1alpha1.FromOwnerReference, - TargetReplicaPath: "/spec/replicas", - CurrentReplicaPath: "/status/replicas", - }, - }, - { - Operation: "replace", - Path: "/spec/validateRules/0/renderedCue", - Value: `data: _ @tag(data) -object: data.object -oldObject: data.oldObject -otherObject: data.extraParams."otherObject" -validate: { - if otherObject.spec.replicas != _|_ { - if otherObject.status.replicas != _|_ { - // target - target * 0.6 > current - if otherObject.spec.replicas-otherObject.spec.replicas*0.6 > otherObject.status.replicas-1 { - { - valid: false - reason: "Cannot delete this pod, cause of hitting maxUnavailable(0.6)" - } - } - } - } -} `, }, }, @@ -1417,3 +1203,28 @@ validate: { }) } } + +func Test_policyInterrupterImpl_OnStartUp(t *testing.T) { + policyInterrupter, err := test_fakePolicyInterrupterManager(t) + if err != nil { + t.Error(err) + return + } + + tests := []struct { + name string + wantErr bool + }{ + { + name: "1", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := policyInterrupter.OnStartUp(); (err != nil) != tt.wantErr { + t.Errorf("OnStartUp() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/utils/templatemanager/templates/validate.go.tmpl b/utils/templatemanager/templates/validate.go.tmpl index 7390360..21a5d23 100644 --- a/utils/templatemanager/templates/validate.go.tmpl +++ b/utils/templatemanager/templates/validate.go.tmpl @@ -3,9 +3,6 @@ {{if and (eq .Type "condition") (.Condition)}} {{template "ConditionTemplate" .Condition}} {{end}} - {{if and (eq .Type "pab") (.PAB)}} - {{template "PABTemplate" .PAB}} - {{end}} {{end}} {{define "ConditionTemplate"}} @@ -80,60 +77,3 @@ validate:{ {{end}} {{end}} {{end}} - -{{define "PABTemplate"}} -{{- /*gotype:github.com/k-cloud-labs/pkg/utils/interrupter/model.PodAvailableBadge*/ -}} -data: _ @tag(data) -object: data.object -oldObject: data.oldObject -{{if .ReplicaReference}} - {{if or (eq .ReplicaReference.From "k8s") (eq .ReplicaReference.From "http") (eq .ReplicaReference.From "owner")}} - {{.ReplicaReference.CueObjectKey}} : data.extraParams."{{.ReplicaReference.CueObjectKey}}" - {{end}} -{{end}} - -validate:{ - if {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.TargetReplicaPath}} !=_|_ { - if {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.CurrentReplicaPath}} !=_|_ { - {{if .MaxUnavailable}} - {{if .IsPercentage}} - // target - target * {{.MaxUnavailable}} > current - if {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.TargetReplicaPath}} - {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.TargetReplicaPath}} * {{.MaxUnavailable}} > {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.CurrentReplicaPath}} - 1 { - { - valid: false - reason: "Cannot delete this pod, cause of hitting maxUnavailable({{.MaxUnavailable}})" - } - } - {{else}} - // target - {{.MaxUnavailable}} > current - if {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.TargetReplicaPath}} - {{.MaxUnavailable}} > {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.CurrentReplicaPath}} - 1 { - { - valid: false - reason: "Cannot delete this pod, cause of hitting maxUnavailable({{.MaxUnavailable}})" - } - } - {{end}} - {{else}} - {{/*minAvailable*/}} - {{if .IsPercentage}} - // target * {{.MinAvailable}} > current - if {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.TargetReplicaPath}} * {{.MinAvailable}} > {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.CurrentReplicaPath}} - 1 { - { - valid: false - reason: "Cannot delete this pod, cause of hitting minAvailable({{.MinAvailable}})" - } - } - {{else}} - // {{.MinAvailable}} > current - if {{.MinAvailable}} > {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.CurrentReplicaPath}} - 1 { - { - valid: false - reason: "Cannot delete this pod, cause of hitting maxUnavailable({{.MinAvailable}})" - } - } - {{end}} - {{end}} - } - } -} -{{end}} \ No newline at end of file diff --git a/utils/templatemanager/templates/validate.template.go b/utils/templatemanager/templates/validate.template.go index 624c4f3..cd24a26 100644 --- a/utils/templatemanager/templates/validate.template.go +++ b/utils/templatemanager/templates/validate.template.go @@ -7,9 +7,6 @@ var ValidateTemplate = ` {{if and (eq .Type "condition") (.Condition)}} {{template "ConditionTemplate" .Condition}} {{end}} - {{if and (eq .Type "pab") (.PAB)}} - {{template "PABTemplate" .PAB}} - {{end}} {{end}} {{define "ConditionTemplate"}} @@ -84,61 +81,4 @@ validate:{ {{end}} {{end}} {{end}} - -{{define "PABTemplate"}} -{{- /*gotype:github.com/k-cloud-labs/pkg/utils/interrupter/model.PodAvailableBadge*/ -}} -data: _ @tag(data) -object: data.object -oldObject: data.oldObject -{{if .ReplicaReference}} - {{if or (eq .ReplicaReference.From "k8s") (eq .ReplicaReference.From "http") (eq .ReplicaReference.From "owner")}} - {{.ReplicaReference.CueObjectKey}} : data.extraParams."{{.ReplicaReference.CueObjectKey}}" - {{end}} -{{end}} - -validate:{ - if {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.TargetReplicaPath}} !=_|_ { - if {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.CurrentReplicaPath}} !=_|_ { - {{if .MaxUnavailable}} - {{if .IsPercentage}} - // target - target * {{.MaxUnavailable}} > current - if {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.TargetReplicaPath}} - {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.TargetReplicaPath}} * {{.MaxUnavailable}} > {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.CurrentReplicaPath}} - 1 { - { - valid: false - reason: "Cannot delete this pod, cause of hitting maxUnavailable({{.MaxUnavailable}})" - } - } - {{else}} - // target - {{.MaxUnavailable}} > current - if {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.TargetReplicaPath}} - {{.MaxUnavailable}} > {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.CurrentReplicaPath}} - 1 { - { - valid: false - reason: "Cannot delete this pod, cause of hitting maxUnavailable({{.MaxUnavailable}})" - } - } - {{end}} - {{else}} - {{/*minAvailable*/}} - {{if .IsPercentage}} - // target * {{.MinAvailable}} > current - if {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.TargetReplicaPath}} * {{.MinAvailable}} > {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.CurrentReplicaPath}} - 1 { - { - valid: false - reason: "Cannot delete this pod, cause of hitting minAvailable({{.MinAvailable}})" - } - } - {{else}} - // {{.MinAvailable}} > current - if {{.MinAvailable}} > {{.ReplicaReference.CueObjectKey}}.{{.ReplicaReference.CurrentReplicaPath}} - 1 { - { - valid: false - reason: "Cannot delete this pod, cause of hitting maxUnavailable({{.MinAvailable}})" - } - } - {{end}} - {{end}} - } - } -} -{{end}} ` diff --git a/utils/tokenmanager/fake/tokengenerator.go b/utils/tokenmanager/fake/tokengenerator.go index 3ab3c1a..eb2a287 100644 --- a/utils/tokenmanager/fake/tokengenerator.go +++ b/utils/tokenmanager/fake/tokengenerator.go @@ -37,6 +37,10 @@ func (tg *FakeTokenGeneratorImpl) Generate(_ context.Context) (token string, exp return "token", time.Now().Add(defaultExpireDuration), nil } +func (tg *FakeTokenGeneratorImpl) Equal(t1 tokenmanager.TokenGenerator) bool { + return tg.id == t1.ID() +} + func (tg *FakeTokenGeneratorImpl) ID() string { return tg.id } diff --git a/utils/tokenmanager/tokengenerator.go b/utils/tokenmanager/tokengenerator.go index b80cc40..ea165f5 100644 --- a/utils/tokenmanager/tokengenerator.go +++ b/utils/tokenmanager/tokengenerator.go @@ -13,6 +13,8 @@ import ( type TokenGenerator interface { // Generate fetch new token and return expire time as well. Generate(ctx context.Context) (token string, expireAt time.Time, err error) + // Equal compares two generator + Equal(tg TokenGenerator) bool // ID for identity to where the token belongs. ID() string } @@ -29,7 +31,7 @@ var ( defaultExpireDuration = time.Minute * 5 // 5min as default token expire time. ) -func NewTokenGenerator(authUrl, username, password string, defaultExpire time.Duration) (TokenGenerator, error) { +func NewTokenGenerator(authUrl, username, password string, defaultExpire time.Duration) TokenGenerator { tg := &tokenGeneratorImpl{ authUrl: authUrl, username: username, @@ -38,13 +40,8 @@ func NewTokenGenerator(authUrl, username, password string, defaultExpire time.Du } // parse url - host, err := tg.getHost() - if err != nil { - return nil, err - } - - tg.id = fmt.Sprintf("%s:%s", host, username) - return tg, nil + tg.id = fmt.Sprintf("%s:%s", tg.getHost(), username) + return tg } type Token struct { @@ -102,6 +99,15 @@ func (tg *tokenGeneratorImpl) Generate(ctx context.Context) (token string, expir return t.Token, time.Now().Add(defaultExpireDuration), nil } +func (tg *tokenGeneratorImpl) Equal(t1 TokenGenerator) bool { + v, ok := t1.(*tokenGeneratorImpl) + if !ok { + return false + } + + return tg.authUrl == v.authUrl && tg.username == v.username && tg.password == v.password +} + func noErr(f func() error) { _ = f() } @@ -110,11 +116,11 @@ func (tg *tokenGeneratorImpl) ID() string { return tg.id } -func (tg *tokenGeneratorImpl) getHost() (string, error) { +func (tg *tokenGeneratorImpl) getHost() string { u, err := url.ParseRequestURI(tg.authUrl) if err != nil { - return "", err + return tg.authUrl } - return u.Host, nil + return u.Host } diff --git a/utils/tokenmanager/tokengenerator_test.go b/utils/tokenmanager/tokengenerator_test.go index ad922f0..e75e963 100644 --- a/utils/tokenmanager/tokengenerator_test.go +++ b/utils/tokenmanager/tokengenerator_test.go @@ -42,9 +42,8 @@ func TestNewTokenGenerator(t *testing.T) { defaultExpire time.Duration } tests := []struct { - name string - args args - wantErr bool + name string + args args }{ { name: "normal", @@ -54,7 +53,6 @@ func TestNewTokenGenerator(t *testing.T) { password: "****", defaultExpire: time.Hour, }, - wantErr: false, }, { name: "error", @@ -64,14 +62,13 @@ func TestNewTokenGenerator(t *testing.T) { password: "****", defaultExpire: time.Hour, }, - wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := NewTokenGenerator(tt.args.authUrl, tt.args.username, tt.args.password, tt.args.defaultExpire) - if (err != nil) != tt.wantErr { - t.Errorf("NewTokenGenerator() error = %v, wantErr %v", err, tt.wantErr) + tg := NewTokenGenerator(tt.args.authUrl, tt.args.username, tt.args.password, tt.args.defaultExpire) + if tg == nil { + t.Errorf("NewTokenGenerator() is nil ") return } }) @@ -117,11 +114,7 @@ func Test_tokenGeneratorImpl_Generate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tg, err := NewTokenGenerator(tt.fields.authUrl, tt.fields.username, tt.fields.password, tt.fields.defaultExpireDuration) - if err != nil { - t.Errorf("NewTokenGenerator() error = %v", err) - return - } + tg := NewTokenGenerator(tt.fields.authUrl, tt.fields.username, tt.fields.password, tt.fields.defaultExpireDuration) gotToken, gotExpireAt, err := tg.Generate(tt.args.ctx) if (err != nil) != tt.wantErr { t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr) @@ -162,14 +155,59 @@ func Test_tokenGeneratorImpl_ID(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tg, err := NewTokenGenerator(tt.fields.authUrl, tt.fields.username, tt.fields.password, tt.fields.defaultExpireDuration) - if err != nil { - t.Errorf("NewTokenGenerator() error = %v", err) - return - } + tg := NewTokenGenerator(tt.fields.authUrl, tt.fields.username, tt.fields.password, tt.fields.defaultExpireDuration) if got := tg.ID(); got != tt.want { t.Errorf("ID() = %v, want %v", got, tt.want) } }) } } + +func Test_tokenGeneratorImpl_Equal(t *testing.T) { + type fields struct { + authUrl string + username string + password string + defaultExpireDuration time.Duration + } + type args struct { + t1 TokenGenerator + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "1", + fields: fields{ + authUrl: "http://127.0.0.1:8090/api/v1/auth", + username: "kinitiras", + password: "****", + defaultExpireDuration: time.Hour, + }, + args: args{t1: NewTokenGenerator("http://127.0.0.1:8090/api/v1/auth", "kinitiras", "****", time.Hour)}, + want: true, + }, + { + name: "2", + fields: fields{ + authUrl: "http://127.0.0.1:8090/api/v1/auth", + username: "kinitiras", + password: "****", + defaultExpireDuration: time.Hour, + }, + args: args{t1: NewTokenGenerator("http://127.0.0.1:8090/api/v1/auth", "kinitiras", "**", time.Hour)}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tg := NewTokenGenerator(tt.fields.authUrl, tt.fields.username, tt.fields.password, tt.fields.defaultExpireDuration) + if got := tg.Equal(tt.args.t1); got != tt.want { + t.Errorf("Equal() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/utils/tokenmanager/tokenmanager.go b/utils/tokenmanager/tokenmanager.go index 777ae74..558764b 100644 --- a/utils/tokenmanager/tokenmanager.go +++ b/utils/tokenmanager/tokenmanager.go @@ -6,6 +6,7 @@ import ( "sync" "time" + "golang.org/x/sync/errgroup" "k8s.io/klog/v2" ) @@ -33,13 +34,16 @@ func (t *tokenManagerImpl) AddToken(generator TokenGenerator, ic IdentifiedCallb t.mu.Lock() defer t.mu.Unlock() + klog.V(4).InfoS("addToken", "token.ID", generator.ID(), "callbackAll.ID", ic.ID()) + info, ok := t.tokenMap[generator.ID()] if !ok { info = &tokenMaintainer{ - generator: generator, - callbacks: make(map[string]IdentifiedCallback), - stopChan: make(chan struct{}), - mu: new(sync.RWMutex), + name: generator.ID(), + generator: generator, + callbackMap: sync.Map{}, + stopChan: make(chan struct{}, 1), + valueLock: new(sync.RWMutex), } } @@ -47,12 +51,18 @@ func (t *tokenManagerImpl) AddToken(generator TokenGenerator, ic IdentifiedCallb t.tokenMap[generator.ID()] = info if !ok { go info.daemon() + } else { + // callback immediately + go func() { + _ = info.callback(ic) + }() } } func (t *tokenManagerImpl) RemoveToken(tg TokenGenerator, ic IdentifiedCallback) { t.mu.Lock() defer t.mu.Unlock() + klog.V(4).InfoS("removeToken", "token.ID", tg.ID(), "callbackAll.ID", ic.ID()) info, ok := t.tokenMap[tg.ID()] if !ok { @@ -63,6 +73,8 @@ func (t *tokenManagerImpl) RemoveToken(tg TokenGenerator, ic IdentifiedCallback) return } + klog.V(4).InfoS("stop token", "token.ID", tg.ID(), "callbackAll.ID", ic.ID()) + delete(t.tokenMap, tg.ID()) go info.stop() // block channel } @@ -88,14 +100,16 @@ func NewTokenManager() TokenManager { } type tokenMaintainer struct { + name string generator TokenGenerator + stopChan chan struct{} + + valueLock *sync.RWMutex token string fetchedAt time.Time expireAt time.Time - stopChan chan struct{} - mu *sync.RWMutex - callbacks map[string]IdentifiedCallback + callbackMap sync.Map } func (t *tokenMaintainer) updateCallbacks(ic IdentifiedCallback) { @@ -103,37 +117,45 @@ func (t *tokenMaintainer) updateCallbacks(ic IdentifiedCallback) { return } - t.mu.Lock() - defer t.mu.Unlock() + t.callbackMap.Store(ic.ID(), ic) +} + +func (t *tokenMaintainer) callback(ic IdentifiedCallback) error { + token, expireAt, _ := t.getValues() + if retryErr := retry(func() error { + return ic.Callback(token, expireAt) + }, 3, time.Millisecond*100); retryErr != nil { + klog.ErrorS(retryErr, "retry callback failed", "id", ic.ID()) + return retryErr + } - t.callbacks[ic.ID()] = ic + return nil } -func (t *tokenMaintainer) callback() { - t.mu.RLock() - defer t.mu.RUnlock() +func (t *tokenMaintainer) callbackAll() error { - for id, cb := range t.callbacks { - if err := cb.Callback(t.token, t.expireAt); err != nil { - go func() { - if retryErr := retry(func() error { - return cb.Callback(t.token, t.expireAt) - }, 3, time.Millisecond*100); retryErr != nil { - klog.ErrorS(err, "retry callback failed", "id", id) - } - }() - klog.ErrorS(err, "call refresh token callback error", "id", id) - } - } + eg, _ := errgroup.WithContext(context.Background()) + t.callbackMap.Range(func(_, value any) bool { + ic := value.(IdentifiedCallback) + eg.Go(func() error { + return t.callback(ic) + }) + + return true + }) + + return eg.Wait() } -// return true if callback map is empty +// return true if callbackAll map is empty func (t *tokenMaintainer) removeCallback(ic IdentifiedCallback) bool { - t.mu.Lock() - defer t.mu.Unlock() - - delete(t.callbacks, ic.ID()) - return len(t.callbacks) == 0 + t.callbackMap.Delete(ic.ID()) + var empty = true + t.callbackMap.Range(func(key, value any) bool { + empty = false + return false + }) + return empty } func (t *tokenMaintainer) stop() { @@ -147,46 +169,61 @@ func (t *tokenMaintainer) refreshToken() error { return err } - t.fetchedAt = time.Now() + _, _, fetchedAt := t.getValues() // token must be at least valid for one minute - if expireAt.Before(t.fetchedAt) || expireAt.Sub(t.fetchedAt).Seconds() < 60 { + if expireAt.Before(fetchedAt) || expireAt.Sub(fetchedAt).Seconds() < 60 { return errors.New("token valid duration too short or expired already, please extend the valid time") } + t.setValues(token, expireAt) + return nil +} + +func (t *tokenMaintainer) setValues(token string, expireAt time.Time) { + t.valueLock.Lock() + defer t.valueLock.Unlock() + t.token = token t.expireAt = expireAt - return nil + t.fetchedAt = time.Now() +} + +func (t *tokenMaintainer) getValues() (token string, expireAt, fetchAt time.Time) { + t.valueLock.RLock() + defer t.valueLock.RUnlock() + + return t.token, t.expireAt, t.fetchedAt } func (t *tokenMaintainer) refreshAndCallback() error { if err := t.refreshToken(); err != nil { - klog.ErrorS(err, "refresh token got error", "lastToken", t.token, "id", t.generator.ID()) + klog.ErrorS(err, "refresh token got error", "id", t.generator.ID()) return err } - t.callback() - return nil + return t.callbackAll() } func (t *tokenMaintainer) daemon() { // attempt to refresh token when it started. -loop: for { select { case <-t.stopChan: + klog.V(4).InfoS("token maintainer stop", "name", t.name) return default: - err := t.refreshAndCallback() - if err == nil { - break loop - } - klog.ErrorS(err, "refresh token got error", "lastToken", t.token, "id", t.generator.ID()) - time.Sleep(time.Millisecond * 100) } + err := t.refreshAndCallback() + if err == nil { + break + } + klog.ErrorS(err, "refresh token got error", "id", t.generator.ID()) + time.Sleep(time.Millisecond * 100) } + _, expireAt, fetchedAt := t.getValues() var ( - expireSeconds = t.expireAt.Sub(t.fetchedAt).Seconds() + expireSeconds = expireAt.Sub(fetchedAt).Seconds() duration = expireSeconds / 10 ticker = time.NewTicker(time.Duration(duration) * time.Second) heartBeat = time.NewTicker(time.Second) @@ -196,6 +233,7 @@ loop: for { select { case <-t.stopChan: + klog.V(4).InfoS("token maintainer stop", "name", t.name) return case <-ticker.C: err := t.refreshAndCallback() @@ -208,7 +246,7 @@ loop: if refreshFailed { // will retry 3 times inside if err := t.refreshAndCallback(); err != nil { - klog.ErrorS(err, "refresh token got error", "lastToken", t.token, "id", t.generator.ID()) + klog.ErrorS(err, "refresh token got error", "id", t.generator.ID()) continue } // reset diff --git a/utils/tokenmanager/tokenmanager_test.go b/utils/tokenmanager/tokenmanager_test.go index 989af6e..0f732f8 100644 --- a/utils/tokenmanager/tokenmanager_test.go +++ b/utils/tokenmanager/tokenmanager_test.go @@ -36,6 +36,10 @@ func (tg *test_tokenGeneratorImpl) Generate(_ context.Context) (token string, ex return "token", time.Now().Add(defaultExpireDuration), nil } +func (tg *test_tokenGeneratorImpl) Equal(t1 TokenGenerator) bool { + return tg.id == t1.ID() +} + func (tg *test_tokenGeneratorImpl) ID() string { return tg.id } @@ -76,10 +80,10 @@ func Test_tokenMaintainer_callback(t1 *testing.T) { for _, tt := range tests { t1.Run(tt.name, func(t1 *testing.T) { t := &tokenMaintainer{ - generator: tt.fields.generator, - callbacks: make(map[string]IdentifiedCallback), - stopChan: make(chan struct{}), - mu: new(sync.RWMutex), + generator: tt.fields.generator, + callbackMap: sync.Map{}, + stopChan: make(chan struct{}, 1), + valueLock: new(sync.RWMutex), } t.updateCallbacks(tt.fields.cb) if err := t.refreshToken(); err != nil { @@ -87,7 +91,10 @@ func Test_tokenMaintainer_callback(t1 *testing.T) { return } - t.callback() + if err := t.callbackAll(); err != nil { + t1.Error(err) + return + } }) } } @@ -136,14 +143,6 @@ func Test_tokenMaintainer_refreshAndCallback(t1 *testing.T) { cb: &test_callback{ id: "t1", callback: func(token string, expireAt time.Time) error { - if token == "token" { - return nil - } - - if expireAt.Before(time.Now()) { - return fmt.Errorf("token expired") - } - return fmt.Errorf("token is not correct(%v)", token) }, }, @@ -154,11 +153,12 @@ func Test_tokenMaintainer_refreshAndCallback(t1 *testing.T) { for _, tt := range tests { t1.Run(tt.name, func(t1 *testing.T) { t := &tokenMaintainer{ - generator: tt.fields.generator, - callbacks: make(map[string]IdentifiedCallback), - stopChan: make(chan struct{}), - mu: new(sync.RWMutex), + generator: tt.fields.generator, + callbackMap: sync.Map{}, + stopChan: make(chan struct{}, 1), + valueLock: new(sync.RWMutex), } + t.updateCallbacks(tt.fields.cb) if err := t.refreshAndCallback(); (err != nil) != tt.wantErr { t1.Errorf("refreshAndCallback() error = %v, wantErr %v", err, tt.wantErr) } @@ -220,10 +220,10 @@ func Test_tokenMaintainer_removeCallback(t1 *testing.T) { for _, tt := range tests { t1.Run(tt.name, func(t1 *testing.T) { t := &tokenMaintainer{ - generator: tt.fields.generator, - callbacks: make(map[string]IdentifiedCallback), - stopChan: make(chan struct{}), - mu: new(sync.RWMutex), + generator: tt.fields.generator, + callbackMap: sync.Map{}, + stopChan: make(chan struct{}, 1), + valueLock: new(sync.RWMutex), } t.updateCallbacks(tt.fields.cb) @@ -277,6 +277,7 @@ func Test_tokenManagerImpl_AddToken(t1 *testing.T) { for _, tt := range tests { t1.Run(tt.name, func(t1 *testing.T) { tt.fields.tg.AddToken(tt.args.generator, tt.args.ic) + tt.fields.tg.AddToken(tt.args.generator, tt.args.ic) }) } } diff --git a/utils/validatemanager/validatemanager.go b/utils/validatemanager/validatemanager.go index 6751727..38fef70 100644 --- a/utils/validatemanager/validatemanager.go +++ b/utils/validatemanager/validatemanager.go @@ -140,15 +140,6 @@ func (m *validateManagerImpl) applyValidatePolicy(cvp *policyv1alpha1.ClusterVal } func (m *validateManagerImpl) executeTemplate(params *cue.CueParams, rule *policyv1alpha1.ValidateRuleWithOperation, cvpName string) (*ValidateResult, error) { - if rule.Template.Type == policyv1alpha1.ValidateRuleTypePodAvailableBadge && rule.Template.PodAvailableBadge != nil { - // only calculate running pod - if phase := getPodPhase(params.Object); phase != corev1.PodRunning { - klog.V(4).InfoS("pod phase incorrect", "phase", phase, - "pod", params.Object.GetNamespace()+"/", params.Object.GetName()) - return nil, nil - } - } - extraParams, err := cue.BuildCueParamsViaValidatePolicy(m.dynamicClient, params.Object, rule.Template) if err != nil { metrics.PolicyGotError(cvpName, params.Object.GroupVersionKind(), metrics.ErrTypePrepareCueParams) diff --git a/utils/validatemanager/validatemanager_test.go b/utils/validatemanager/validatemanager_test.go index 72629f9..f4aea34 100644 --- a/utils/validatemanager/validatemanager_test.go +++ b/utils/validatemanager/validatemanager_test.go @@ -11,7 +11,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/klog/v2" policyv1alpha1 "github.com/k-cloud-labs/pkg/apis/policy/v1alpha1" @@ -95,26 +94,6 @@ validate: { `, }}}} - validatePolicy4 := &policyv1alpha1.ClusterValidatePolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "validatepolicy4", - }, - Spec: policyv1alpha1.ClusterValidatePolicySpec{ - ValidateRules: []policyv1alpha1.ValidateRuleWithOperation{ - { - TargetOperations: []admissionv1.Operation{admissionv1.Delete}, - Template: &policyv1alpha1.ValidateRuleTemplate{ - Type: policyv1alpha1.ValidateRuleTypePodAvailableBadge, - PodAvailableBadge: &policyv1alpha1.PodAvailableBadge{ - MaxUnavailable: &intstr.IntOrString{ - Type: intstr.String, - StrVal: "40%", - }, - }, - }, - RenderedCue: ``, - }}}} - tests := []struct { name string operation admissionv1.Operation @@ -165,7 +144,6 @@ validate: { validatePolicy1, validatePolicy2, validatePolicy3, - validatePolicy4, }, nil).AnyTimes() for _, tt := range tests {