Implement ResourceInterpreterCustomization ValidatingAdmission
Signed-off-by: chaunceyjiang <chaunceyjiang@gmail.com>
This commit is contained in:
parent
eb3763c201
commit
0c16431a51
|
@ -139,6 +139,20 @@ webhooks:
|
|||
sideEffects: None
|
||||
admissionReviewVersions: ["v1"]
|
||||
timeoutSeconds: 10
|
||||
- name: resourceinterpretercustomization.karmada.io
|
||||
rules:
|
||||
- operations: ["CREATE", "UPDATE"]
|
||||
apiGroups: ["config.karmada.io"]
|
||||
apiVersions: ["*"]
|
||||
resources: ["resourceinterpretercustomizations"]
|
||||
scope: "Cluster"
|
||||
clientConfig:
|
||||
url: https://karmada-webhook.karmada-system.svc:443/validate-resourceinterpretercustomization
|
||||
caBundle: {{caBundle}}
|
||||
failurePolicy: Fail
|
||||
sideEffects: None
|
||||
admissionReviewVersions: ["v1"]
|
||||
timeoutSeconds: 10
|
||||
- name: federatedresourcequota.karmada.io
|
||||
rules:
|
||||
- operations: ["CREATE", "UPDATE"]
|
||||
|
|
|
@ -143,4 +143,18 @@ webhooks:
|
|||
sideEffects: None
|
||||
admissionReviewVersions: ["v1"]
|
||||
timeoutSeconds: 3
|
||||
- name: resourceinterpretercustomization.karmada.io
|
||||
rules:
|
||||
- operations: ["CREATE", "UPDATE"]
|
||||
apiGroups: ["config.karmada.io"]
|
||||
apiVersions: ["*"]
|
||||
resources: ["resourceinterpretercustomizations"]
|
||||
scope: "Cluster"
|
||||
clientConfig:
|
||||
url: https://{{ $name }}-webhook.{{ $namespace }}.svc:443/validate-resourceinterpretercustomization
|
||||
{{- include "karmada.webhook.caBundle" . | nindent 6 }}
|
||||
failurePolicy: Fail
|
||||
sideEffects: None
|
||||
admissionReviewVersions: ["v1"]
|
||||
timeoutSeconds: 3
|
||||
{{- end -}}
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/karmada-io/karmada/pkg/webhook/federatedresourcequota"
|
||||
"github.com/karmada-io/karmada/pkg/webhook/overridepolicy"
|
||||
"github.com/karmada-io/karmada/pkg/webhook/propagationpolicy"
|
||||
"github.com/karmada-io/karmada/pkg/webhook/resourceinterpretercustomization"
|
||||
"github.com/karmada-io/karmada/pkg/webhook/work"
|
||||
)
|
||||
|
||||
|
@ -125,6 +126,7 @@ func Run(ctx context.Context, opts *options.Options) error {
|
|||
hookServer.Register("/convert", &conversion.Webhook{})
|
||||
hookServer.Register("/validate-resourceinterpreterwebhookconfiguration", &webhook.Admission{Handler: &configuration.ValidatingAdmission{}})
|
||||
hookServer.Register("/validate-federatedresourcequota", &webhook.Admission{Handler: &federatedresourcequota.ValidatingAdmission{}})
|
||||
hookServer.Register("/validate-resourceinterpretercustomization", &webhook.Admission{Handler: &resourceinterpretercustomization.ValidatingAdmission{Client: hookManager.GetClient()}})
|
||||
hookServer.WebhookMux.Handle("/readyz/", http.StripPrefix("/readyz/", &healthz.Handler{}))
|
||||
|
||||
// blocks until the context is done.
|
||||
|
|
|
@ -157,7 +157,22 @@ webhooks:
|
|||
failurePolicy: Fail
|
||||
sideEffects: None
|
||||
admissionReviewVersions: ["v1"]
|
||||
timeoutSeconds: 3`, systemNamespace, caBundle)
|
||||
timeoutSeconds: 3
|
||||
- name: resourceinterpretercustomization.karmada.io
|
||||
rules:
|
||||
- operations: ["CREATE", "UPDATE"]
|
||||
apiGroups: ["config.karmada.io"]
|
||||
apiVersions: ["*"]
|
||||
resources: ["resourceexploringwebhookconfigurations"]
|
||||
scope: "Cluster"
|
||||
clientConfig:
|
||||
url: https://karmada-webhook.%[1]s.svc:443/validate-resourceinterpretercustomization
|
||||
caBundle: %[2]s
|
||||
failurePolicy: Fail
|
||||
sideEffects: None
|
||||
admissionReviewVersions: ["v1"]
|
||||
timeoutSeconds: 3
|
||||
`, systemNamespace, caBundle)
|
||||
}
|
||||
|
||||
func createOrUpdateValidatingWebhookConfiguration(c kubernetes.Interface, staticYaml string) error {
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
|
||||
"github.com/karmada-io/karmada/pkg/karmadactl/util/genericresource"
|
||||
"github.com/karmada-io/karmada/pkg/resourceinterpreter/configurableinterpreter"
|
||||
"github.com/karmada-io/karmada/pkg/util/interpreter"
|
||||
)
|
||||
|
||||
func (o *Options) completeExecute(f util.Factory) []error {
|
||||
|
@ -72,7 +73,7 @@ func (o *Options) runExecute() error {
|
|||
return fmt.Errorf("fail to get status items: %v", err)
|
||||
}
|
||||
|
||||
args := ruleArgs{
|
||||
args := interpreter.RuleArgs{
|
||||
Desired: desired,
|
||||
Observed: observed,
|
||||
Status: status,
|
||||
|
@ -92,7 +93,7 @@ func (o *Options) runExecute() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func printExecuteResult(w, errOut io.Writer, name string, result *ruleResult) {
|
||||
func printExecuteResult(w, errOut io.Writer, name string, result *interpreter.RuleResult) {
|
||||
if result.Err != nil {
|
||||
fmt.Fprintf(errOut, "Execute %s error: %v\n", name, result.Err)
|
||||
return
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/karmada-io/karmada/pkg/karmadactl/util/genericresource"
|
||||
"github.com/karmada-io/karmada/pkg/util/gclient"
|
||||
"github.com/karmada-io/karmada/pkg/util/helper"
|
||||
"github.com/karmada-io/karmada/pkg/util/interpreter"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -72,7 +73,7 @@ const (
|
|||
func NewCmdInterpret(f util.Factory, parentCommand string, streams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := &Options{
|
||||
IOStreams: streams,
|
||||
Rules: allRules,
|
||||
Rules: interpreter.AllResourceInterpreterCustomizationRules,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "interpret (-f FILENAME) (--operation OPERATION) [--ARGS VALUE]... ",
|
||||
|
@ -123,7 +124,7 @@ type Options struct {
|
|||
ObservedResult *resource.Result
|
||||
StatusResult *genericresource.Result
|
||||
|
||||
Rules Rules
|
||||
Rules interpreter.Rules
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package interpret
|
||||
package interpreter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -11,7 +11,8 @@ import (
|
|||
"github.com/karmada-io/karmada/pkg/resourceinterpreter/configurableinterpreter"
|
||||
)
|
||||
|
||||
var allRules = []Rule{
|
||||
// AllResourceInterpreterCustomizationRules all InterpreterOperations
|
||||
var AllResourceInterpreterCustomizationRules = []Rule{
|
||||
&retentionRule{},
|
||||
&replicaResourceRule{},
|
||||
&replicaRevisionRule{},
|
||||
|
@ -46,7 +47,7 @@ func (r *retentionRule) SetScript(c *configv1alpha1.ResourceInterpreterCustomiza
|
|||
c.Spec.Customizations.Retention.LuaScript = script
|
||||
}
|
||||
|
||||
func (r *retentionRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args ruleArgs) *ruleResult {
|
||||
func (r *retentionRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args RuleArgs) *RuleResult {
|
||||
desired, err := args.getDesiredObjectOrError()
|
||||
if err != nil {
|
||||
return newRuleResultWithError(err)
|
||||
|
@ -91,7 +92,7 @@ func (r *replicaResourceRule) SetScript(c *configv1alpha1.ResourceInterpreterCus
|
|||
c.Spec.Customizations.ReplicaResource.LuaScript = script
|
||||
}
|
||||
|
||||
func (r *replicaResourceRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args ruleArgs) *ruleResult {
|
||||
func (r *replicaResourceRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args RuleArgs) *RuleResult {
|
||||
obj, err := args.getObjectOrError()
|
||||
if err != nil {
|
||||
return newRuleResultWithError(err)
|
||||
|
@ -132,7 +133,7 @@ func (r *replicaRevisionRule) SetScript(c *configv1alpha1.ResourceInterpreterCus
|
|||
c.Spec.Customizations.ReplicaRevision.LuaScript = script
|
||||
}
|
||||
|
||||
func (r *replicaRevisionRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args ruleArgs) *ruleResult {
|
||||
func (r *replicaRevisionRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args RuleArgs) *RuleResult {
|
||||
obj, err := args.getObjectOrError()
|
||||
if err != nil {
|
||||
return newRuleResultWithError(err)
|
||||
|
@ -173,7 +174,7 @@ func (s *statusReflectionRule) SetScript(c *configv1alpha1.ResourceInterpreterCu
|
|||
c.Spec.Customizations.StatusReflection.LuaScript = script
|
||||
}
|
||||
|
||||
func (s *statusReflectionRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args ruleArgs) *ruleResult {
|
||||
func (s *statusReflectionRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args RuleArgs) *RuleResult {
|
||||
obj, err := args.getObjectOrError()
|
||||
if err != nil {
|
||||
return newRuleResultWithError(err)
|
||||
|
@ -214,7 +215,7 @@ func (s *statusAggregationRule) SetScript(c *configv1alpha1.ResourceInterpreterC
|
|||
c.Spec.Customizations.StatusAggregation.LuaScript = script
|
||||
}
|
||||
|
||||
func (s *statusAggregationRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args ruleArgs) *ruleResult {
|
||||
func (s *statusAggregationRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args RuleArgs) *RuleResult {
|
||||
obj, err := args.getObjectOrError()
|
||||
if err != nil {
|
||||
return newRuleResultWithError(err)
|
||||
|
@ -260,7 +261,7 @@ func (h *healthInterpretationRule) SetScript(c *configv1alpha1.ResourceInterpret
|
|||
c.Spec.Customizations.HealthInterpretation.LuaScript = script
|
||||
}
|
||||
|
||||
func (h *healthInterpretationRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args ruleArgs) *ruleResult {
|
||||
func (h *healthInterpretationRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args RuleArgs) *RuleResult {
|
||||
obj, err := args.getObjectOrError()
|
||||
if err != nil {
|
||||
return newRuleResultWithError(err)
|
||||
|
@ -301,7 +302,7 @@ func (d *dependencyInterpretationRule) SetScript(c *configv1alpha1.ResourceInter
|
|||
c.Spec.Customizations.DependencyInterpretation.LuaScript = script
|
||||
}
|
||||
|
||||
func (d *dependencyInterpretationRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args ruleArgs) *ruleResult {
|
||||
func (d *dependencyInterpretationRule) Run(interpreter *configurableinterpreter.ConfigurableInterpreter, args RuleArgs) *RuleResult {
|
||||
obj, err := args.getObjectOrError()
|
||||
if err != nil {
|
||||
return newRuleResultWithError(err)
|
||||
|
@ -325,7 +326,7 @@ type Rule interface {
|
|||
// SetScript set the script for the rule. If script is empty, disable the rule.
|
||||
SetScript(*configv1alpha1.ResourceInterpreterCustomization, string)
|
||||
// Run execute the rule with given args, and return the result.
|
||||
Run(*configurableinterpreter.ConfigurableInterpreter, ruleArgs) *ruleResult
|
||||
Run(*configurableinterpreter.ConfigurableInterpreter, RuleArgs) *RuleResult
|
||||
}
|
||||
|
||||
// Rules is a series of rules.
|
||||
|
@ -365,28 +366,29 @@ func (r Rules) Get(name string) Rule {
|
|||
return nil
|
||||
}
|
||||
|
||||
type ruleArgs struct {
|
||||
// RuleArgs rule execution args.
|
||||
type RuleArgs struct {
|
||||
Desired *unstructured.Unstructured
|
||||
Observed *unstructured.Unstructured
|
||||
Status []workv1alpha2.AggregatedStatusItem
|
||||
Replica int64
|
||||
}
|
||||
|
||||
func (r ruleArgs) getDesiredObjectOrError() (*unstructured.Unstructured, error) {
|
||||
func (r RuleArgs) getDesiredObjectOrError() (*unstructured.Unstructured, error) {
|
||||
if r.Desired == nil {
|
||||
return nil, fmt.Errorf("desired, desired-file options are not set")
|
||||
}
|
||||
return r.Desired, nil
|
||||
}
|
||||
|
||||
func (r ruleArgs) getObservedObjectOrError() (*unstructured.Unstructured, error) {
|
||||
func (r RuleArgs) getObservedObjectOrError() (*unstructured.Unstructured, error) {
|
||||
if r.Observed == nil {
|
||||
return nil, fmt.Errorf("observed, observed-file options are not set")
|
||||
}
|
||||
return r.Observed, nil
|
||||
}
|
||||
|
||||
func (r ruleArgs) getObjectOrError() (*unstructured.Unstructured, error) {
|
||||
func (r RuleArgs) getObjectOrError() (*unstructured.Unstructured, error) {
|
||||
if r.Desired == nil && r.Observed == nil {
|
||||
return nil, fmt.Errorf("desired-file, observed-file options are not set")
|
||||
}
|
||||
|
@ -399,27 +401,29 @@ func (r ruleArgs) getObjectOrError() (*unstructured.Unstructured, error) {
|
|||
return r.Observed, nil
|
||||
}
|
||||
|
||||
type nameValue struct {
|
||||
// NameValue name and value.
|
||||
type NameValue struct {
|
||||
Name string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
type ruleResult struct {
|
||||
Results []nameValue
|
||||
// RuleResult rule execution result.
|
||||
type RuleResult struct {
|
||||
Results []NameValue
|
||||
Err error
|
||||
}
|
||||
|
||||
func newRuleResult() *ruleResult {
|
||||
return &ruleResult{}
|
||||
func newRuleResult() *RuleResult {
|
||||
return &RuleResult{}
|
||||
}
|
||||
|
||||
func newRuleResultWithError(err error) *ruleResult {
|
||||
return &ruleResult{
|
||||
func newRuleResultWithError(err error) *RuleResult {
|
||||
return &RuleResult{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ruleResult) add(name string, value interface{}) *ruleResult {
|
||||
r.Results = append(r.Results, nameValue{Name: name, Value: value})
|
||||
func (r *RuleResult) add(name string, value interface{}) *RuleResult {
|
||||
r.Results = append(r.Results, NameValue{Name: name, Value: value})
|
||||
return r
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package resourceinterpretercustomization
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
|
||||
"github.com/karmada-io/karmada/pkg/resourceinterpreter/configurableinterpreter/luavm"
|
||||
"github.com/karmada-io/karmada/pkg/util/interpreter"
|
||||
)
|
||||
|
||||
func validateCustomizationRule(oldRules, newRules *configv1alpha1.ResourceInterpreterCustomization) error {
|
||||
if oldRules.Spec.Target.APIVersion != newRules.Spec.Target.APIVersion ||
|
||||
oldRules.Spec.Target.Kind != newRules.Spec.Target.Kind {
|
||||
return nil
|
||||
}
|
||||
for _, rule := range interpreter.AllResourceInterpreterCustomizationRules {
|
||||
oldScript := rule.GetScript(oldRules)
|
||||
newScript := rule.GetScript(newRules)
|
||||
if oldScript != "" && newScript != "" {
|
||||
return fmt.Errorf("conflicting with InterpreterOperation(%s) of existing ResourceInterpreterCustomization(%s)", rule.Name(), oldRules.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkCustomizationsRule(customization *configv1alpha1.ResourceInterpreterCustomization) error {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
|
||||
defer cancel()
|
||||
l, err := luavm.NewWithContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer l.Close()
|
||||
for _, rule := range interpreter.AllResourceInterpreterCustomizationRules {
|
||||
if script := rule.GetScript(customization); script != "" {
|
||||
if _, err = l.LoadString(script); err != nil {
|
||||
return fmt.Errorf("InterpreterOperation(%s) Lua script error: %v", rule.Name(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateResourceInterpreterCustomizations(newConfig *configv1alpha1.ResourceInterpreterCustomization, customizations *configv1alpha1.ResourceInterpreterCustomizationList) error {
|
||||
for _, config := range customizations.Items {
|
||||
// skip self verification
|
||||
if config.Name == newConfig.Name {
|
||||
continue
|
||||
}
|
||||
oldConfig := config
|
||||
if err := validateCustomizationRule(&oldConfig, newConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return checkCustomizationsRule(newConfig)
|
||||
}
|
|
@ -0,0 +1,477 @@
|
|||
package resourceinterpretercustomization
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
|
||||
)
|
||||
|
||||
func Test_validateCustomizationRule(t *testing.T) {
|
||||
type args struct {
|
||||
oldRules *configv1alpha1.ResourceInterpreterCustomization
|
||||
newRules *configv1alpha1.ResourceInterpreterCustomization
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "the different Kind of ResourceInterpreterCustomization",
|
||||
args: args{
|
||||
oldRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
},
|
||||
},
|
||||
newRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: " the different APIVersion of ResourceInterpreterCustomization",
|
||||
args: args{
|
||||
oldRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
},
|
||||
},
|
||||
newRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v2",
|
||||
Kind: "kind",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "the same InterpreterOperation(Retention) of ResourceInterpreterCustomization",
|
||||
args: args{
|
||||
oldRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
Retention: &configv1alpha1.LocalValueRetention{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
newRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
Retention: &configv1alpha1.LocalValueRetention{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "the same InterpreterOperation(ReplicaResource) of ResourceInterpreterCustomization",
|
||||
args: args{
|
||||
oldRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
ReplicaResource: &configv1alpha1.ReplicaResourceRequirement{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
newRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
ReplicaResource: &configv1alpha1.ReplicaResourceRequirement{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "the same InterpreterOperation(ReplicaRevision) of ResourceInterpreterCustomization",
|
||||
args: args{
|
||||
oldRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
ReplicaRevision: &configv1alpha1.ReplicaRevision{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
newRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
ReplicaRevision: &configv1alpha1.ReplicaRevision{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "the same InterpreterOperation(StatusReflection) of ResourceInterpreterCustomization",
|
||||
args: args{
|
||||
oldRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
StatusReflection: &configv1alpha1.StatusReflection{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
newRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
StatusReflection: &configv1alpha1.StatusReflection{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "the same InterpreterOperation(StatusAggregation) of ResourceInterpreterCustomization",
|
||||
args: args{
|
||||
oldRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
StatusAggregation: &configv1alpha1.StatusAggregation{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
newRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
StatusAggregation: &configv1alpha1.StatusAggregation{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "the same InterpreterOperation(HealthInterpretation) of ResourceInterpreterCustomization",
|
||||
args: args{
|
||||
oldRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
HealthInterpretation: &configv1alpha1.HealthInterpretation{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
newRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
HealthInterpretation: &configv1alpha1.HealthInterpretation{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "the same InterpreterOperation(DependencyInterpretation) of ResourceInterpreterCustomization",
|
||||
args: args{
|
||||
oldRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
DependencyInterpretation: &configv1alpha1.DependencyInterpretation{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
newRules: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
},
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
DependencyInterpretation: &configv1alpha1.DependencyInterpretation{LuaScript: "LuaScript"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := validateCustomizationRule(tt.args.oldRules, tt.args.newRules); (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateCustomizationRule() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateResourceInterpreterCustomizations(t *testing.T) {
|
||||
type args struct {
|
||||
customization *configv1alpha1.ResourceInterpreterCustomization
|
||||
customizations *configv1alpha1.ResourceInterpreterCustomizationList
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "the same name of ResourceInterpreterCustomization",
|
||||
args: args{
|
||||
customization: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
}, Customizations: configv1alpha1.CustomizationRules{Retention: &configv1alpha1.LocalValueRetention{LuaScript: `function Retain(desiredObj, observedObj) end`}}}},
|
||||
customizations: &configv1alpha1.ResourceInterpreterCustomizationList{
|
||||
Items: []configv1alpha1.ResourceInterpreterCustomization{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{Target: configv1alpha1.CustomizationTarget{
|
||||
APIVersion: "foo/v1",
|
||||
Kind: "kind",
|
||||
}, Customizations: configv1alpha1.CustomizationRules{Retention: &configv1alpha1.LocalValueRetention{LuaScript: "function Retain(desiredObj, observedObj) end"}}}}}},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := validateResourceInterpreterCustomizations(tt.args.customization, tt.args.customizations); (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateResourceInterpreterCustomizations() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checkCustomizationsRule(t *testing.T) {
|
||||
type args struct {
|
||||
customization *configv1alpha1.ResourceInterpreterCustomization
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "correct lua script",
|
||||
args: args{customization: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
Retention: &configv1alpha1.LocalValueRetention{LuaScript: `
|
||||
function Retain(desiredObj, observedObj)
|
||||
desiredObj.spec.fieldFoo = observedObj.spec.fieldFoo
|
||||
return desiredObj
|
||||
end
|
||||
`},
|
||||
ReplicaResource: &configv1alpha1.ReplicaResourceRequirement{LuaScript: `
|
||||
function GetReplicas(desiredObj)
|
||||
nodeClaim = {}
|
||||
resourceRequest = {}
|
||||
result = {}
|
||||
|
||||
result.replica = desiredObj.spec.replicas
|
||||
result.resourceRequest = desiredObj.spec.template.spec.containers[0].resources.limits
|
||||
|
||||
nodeClaim.nodeSelector = desiredObj.spec.template.spec.nodeSelector
|
||||
nodeClaim.tolerations = desiredObj.spec.template.spec.tolerations
|
||||
result.nodeClaim = nodeClaim
|
||||
|
||||
return result
|
||||
end
|
||||
`},
|
||||
ReplicaRevision: &configv1alpha1.ReplicaRevision{LuaScript: `
|
||||
function ReviseReplica(desiredObj, desiredReplica)
|
||||
desiredObj.spec.replicas = desiredReplica
|
||||
return desiredObj
|
||||
end
|
||||
`},
|
||||
StatusReflection: &configv1alpha1.StatusReflection{LuaScript: `
|
||||
function ReflectStatus(observedObj)
|
||||
status = {}
|
||||
status.readyReplicas = observedObj.status.observedObj
|
||||
return status
|
||||
end
|
||||
|
||||
`},
|
||||
StatusAggregation: &configv1alpha1.StatusAggregation{LuaScript: `
|
||||
function AggregateStatus(desiredObj, statusItems)
|
||||
for i = 1, #items do
|
||||
desiredObj.status.readyReplicas = desiredObj.status.readyReplicas + items[i].readyReplicas
|
||||
end
|
||||
return desiredObj
|
||||
end
|
||||
|
||||
`},
|
||||
HealthInterpretation: &configv1alpha1.HealthInterpretation{LuaScript: `
|
||||
function InterpretHealth(observedObj)
|
||||
if observedObj.status.readyReplicas == observedObj.spec.replicas then
|
||||
return true
|
||||
end
|
||||
end
|
||||
`},
|
||||
DependencyInterpretation: &configv1alpha1.DependencyInterpretation{LuaScript: `
|
||||
function GetDependencies(desiredObj)
|
||||
dependencies = {}
|
||||
if desiredObj.spec.serviceAccountName ~= "" and desiredObj.spec.serviceAccountName ~= "default" then
|
||||
dependency = {}
|
||||
dependency.apiVersion = "v1"
|
||||
dependency.kind = "ServiceAccount"
|
||||
dependency.name = desiredObj.spec.serviceAccountName
|
||||
dependency.namespace = desiredObj.namespace
|
||||
dependencies[0] = {}
|
||||
dependencies[0] = dependency
|
||||
end
|
||||
return dependencies
|
||||
end
|
||||
|
||||
`}},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Retention contains the wrong lua script",
|
||||
args: args{customization: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
Retention: &configv1alpha1.LocalValueRetention{LuaScript: `function Retain(desiredObj, observedObj)`},
|
||||
},
|
||||
},
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ReplicaResource contains the wrong lua script",
|
||||
args: args{customization: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
ReplicaResource: &configv1alpha1.ReplicaResourceRequirement{LuaScript: `function GetReplicas(desiredObj)`},
|
||||
},
|
||||
},
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ReplicaRevision contains the wrong lua script",
|
||||
args: args{customization: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
ReplicaRevision: &configv1alpha1.ReplicaRevision{LuaScript: `function ReviseReplica(desiredObj, desiredReplica)`}},
|
||||
},
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "StatusReflection contains the wrong lua script",
|
||||
args: args{customization: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
StatusReflection: &configv1alpha1.StatusReflection{LuaScript: `function ReflectStatus(observedObj)`}},
|
||||
},
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "StatusAggregation contains the wrong lua script",
|
||||
args: args{customization: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
StatusAggregation: &configv1alpha1.StatusAggregation{LuaScript: `function AggregateStatus(desiredObj, statusItems)`}},
|
||||
},
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "HealthInterpretation contains the wrong lua script",
|
||||
args: args{customization: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
HealthInterpretation: &configv1alpha1.HealthInterpretation{LuaScript: `function InterpretHealth(observedObj)`}},
|
||||
},
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "DependencyInterpretation contains the wrong lua script",
|
||||
args: args{customization: &configv1alpha1.ResourceInterpreterCustomization{
|
||||
Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{
|
||||
Customizations: configv1alpha1.CustomizationRules{
|
||||
DependencyInterpretation: &configv1alpha1.DependencyInterpretation{LuaScript: `function GetDependencies(desiredObj)`}},
|
||||
},
|
||||
}},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := checkCustomizationsRule(tt.args.customization); (err != nil) != tt.wantErr {
|
||||
t.Errorf("checkCustomizationsRule() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package resourceinterpretercustomization
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
|
||||
)
|
||||
|
||||
// Check if our ValidatingAdmission implements necessary interface
|
||||
var _ admission.Handler = &ValidatingAdmission{}
|
||||
var _ admission.DecoderInjector = &ValidatingAdmission{}
|
||||
|
||||
// ValidatingAdmission validates ResourceInterpreterCustomization object when creating/updating.
|
||||
type ValidatingAdmission struct {
|
||||
client.Client
|
||||
decoder *admission.Decoder
|
||||
}
|
||||
|
||||
// Handle implements admission.Handler interface.
|
||||
// It yields a response to an AdmissionRequest.
|
||||
func (v *ValidatingAdmission) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
configuration := &configv1alpha1.ResourceInterpreterCustomization{}
|
||||
|
||||
err := v.decoder.Decode(req, configuration)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
klog.V(2).Infof("Validating ResourceInterpreterCustomization(%s) for request: %s", configuration.Name, req.Operation)
|
||||
configs := &configv1alpha1.ResourceInterpreterCustomizationList{}
|
||||
if err = v.List(ctx, configs); err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, err)
|
||||
}
|
||||
if err = validateResourceInterpreterCustomizations(configuration, configs); err != nil {
|
||||
return admission.Denied(err.Error())
|
||||
}
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
// InjectDecoder implements admission.DecoderInjector interface.
|
||||
// A decoder will be automatically injected.
|
||||
func (v *ValidatingAdmission) InjectDecoder(decoder *admission.Decoder) error {
|
||||
v.decoder = decoder
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue