add webhook for clusterpropagationpolicy and propagationpolicy (#201)

Signed-off-by: lihanbo <lihanbo2@huawei.com>
This commit is contained in:
Hanbo Li 2021-03-10 09:36:32 +08:00 committed by GitHub
parent 614d7b0b8d
commit d48065f086
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 201 additions and 25 deletions

View File

@ -19,6 +19,20 @@ webhooks:
sideEffects: None
admissionReviewVersions: ["v1beta1"]
timeoutSeconds: 3
- name: clusterpropagationpolicy.karmada.io
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["policy.karmada.io"]
apiVersions: ["*"]
resources: ["clusterpropagationpolicies"]
scope: "Cluster"
clientConfig:
url: https://karmada-webhook.karmada-system.svc:443/mutate-clusterpropagationpolicy
caBundle: {{caBundle}}
failurePolicy: Fail
sideEffects: None
admissionReviewVersions: ["v1beta1"]
timeoutSeconds: 3
- name: overridepolicy.karmada.io
rules:
- operations: ["CREATE", "UPDATE"]
@ -69,3 +83,17 @@ webhooks:
sideEffects: None
admissionReviewVersions: ["v1beta1"]
timeoutSeconds: 3
- name: clusterpropagationpolicy.karmada.io
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["policy.karmada.io"]
apiVersions: ["*"]
resources: ["clusterpropagationpolicies"]
scope: "Cluster"
clientConfig:
url: https://karmada-webhook.karmada-system.svc:443/validate-clusterpropagationpolicy
caBundle: {{caBundle}}
failurePolicy: Fail
sideEffects: None
admissionReviewVersions: ["v1beta1"]
timeoutSeconds: 3

View File

@ -16,6 +16,7 @@ import (
"github.com/karmada-io/karmada/cmd/webhook/app/options"
"github.com/karmada-io/karmada/pkg/util/gclient"
"github.com/karmada-io/karmada/pkg/webhook/cluster"
"github.com/karmada-io/karmada/pkg/webhook/clusterpropagationpolicy"
"github.com/karmada-io/karmada/pkg/webhook/overridepolicy"
"github.com/karmada-io/karmada/pkg/webhook/propagationpolicy"
)
@ -69,6 +70,8 @@ func Run(opts *options.Options, stopChan <-chan struct{}) error {
hookServer.Register("/validate-cluster", &webhook.Admission{Handler: &cluster.ValidatingAdmission{}})
hookServer.Register("/mutate-propagationpolicy", &webhook.Admission{Handler: &propagationpolicy.MutatingAdmission{}})
hookServer.Register("/validate-propagationpolicy", &webhook.Admission{Handler: &propagationpolicy.ValidatingAdmission{}})
hookServer.Register("/mutate-clusterpropagationpolicy", &webhook.Admission{Handler: &clusterpropagationpolicy.MutatingAdmission{}})
hookServer.Register("/validate-clusterpropagationpolicy", &webhook.Admission{Handler: &clusterpropagationpolicy.ValidatingAdmission{}})
hookServer.Register("/mutate-overridepolicy", &webhook.Admission{Handler: &overridepolicy.MutatingAdmission{}})
hookServer.WebhookMux.Handle("/readyz/", http.StripPrefix("/readyz/", &healthz.Handler{}))

43
pkg/util/helper/policy.go Normal file
View File

@ -0,0 +1,43 @@
package helper
import (
"fmt"
"k8s.io/klog/v2"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
)
// DenyReasonResourceSelectorsModify constructs a reason indicating that modify ResourceSelectors is not allowed.
const DenyReasonResourceSelectorsModify = "modify ResourceSelectors is forbidden"
// SetDefaultSpreadConstraints set default spread constraints if both 'SpreadByField' and 'SpreadByLabel' not set.
func SetDefaultSpreadConstraints(spreadConstraints []policyv1alpha1.SpreadConstraint) {
for i := range spreadConstraints {
if len(spreadConstraints[i].SpreadByLabel) == 0 && len(spreadConstraints[i].SpreadByField) == 0 {
klog.Infof("Setting default SpreadByField with %s", policyv1alpha1.SpreadByFieldCluster)
spreadConstraints[i].SpreadByField = policyv1alpha1.SpreadByFieldCluster
}
if spreadConstraints[i].MinGroups == 0 {
klog.Infof("Setting default MinGroups to 1")
spreadConstraints[i].MinGroups = 1
}
}
}
// ValidateSpreadConstraint tests if the constraints is valid.
func ValidateSpreadConstraint(spreadConstraints []policyv1alpha1.SpreadConstraint) error {
// SpreadByField and SpreadByLabel should not co-exist
for _, constraint := range spreadConstraints {
if len(constraint.SpreadByField) > 0 && len(constraint.SpreadByLabel) > 0 {
return fmt.Errorf("invalid constraints: SpreadByLabel(%s) should not co-exist with spreadByField(%s)", constraint.SpreadByLabel, constraint.SpreadByField)
}
// If MaxGroups provided, it should greater or equal than MinGroups.
if constraint.MaxGroups > 0 && constraint.MaxGroups < constraint.MinGroups {
return fmt.Errorf("maxGroups(%d) lower than minGroups(%d) is not allowed", constraint.MaxGroups, constraint.MinGroups)
}
}
return nil
}

View File

@ -0,0 +1,48 @@
package clusterpropagationpolicy
import (
"context"
"encoding/json"
"net/http"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
"github.com/karmada-io/karmada/pkg/util/helper"
)
// MutatingAdmission mutates API request if necessary.
type MutatingAdmission struct {
decoder *admission.Decoder
}
// Check if our MutatingAdmission implements necessary interface
var _ admission.Handler = &MutatingAdmission{}
var _ admission.DecoderInjector = &MutatingAdmission{}
// Handle yields a response to an AdmissionRequest.
func (a *MutatingAdmission) Handle(ctx context.Context, req admission.Request) admission.Response {
policy := &policyv1alpha1.ClusterPropagationPolicy{}
err := a.decoder.Decode(req, policy)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
// Set default spread constraints if both 'SpreadByField' and 'SpreadByLabel' not set.
helper.SetDefaultSpreadConstraints(policy.Spec.Placement.SpreadConstraints)
marshaledBytes, err := json.Marshal(policy)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
return admission.PatchResponseFromRaw(req.Object.Raw, marshaledBytes)
}
// InjectDecoder implements admission.DecoderInjector interface.
// A decoder will be automatically injected.
func (a *MutatingAdmission) InjectDecoder(d *admission.Decoder) error {
a.decoder = d
return nil
}

View File

@ -0,0 +1,61 @@
package clusterpropagationpolicy
import (
"context"
"net/http"
"k8s.io/api/admission/v1beta1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
"github.com/karmada-io/karmada/pkg/util/helper"
)
// ValidatingAdmission validates ClusterPropagationPolicy object when creating/updating/deleting.
type ValidatingAdmission struct {
decoder *admission.Decoder
}
// Check if our ValidatingAdmission implements necessary interface
var _ admission.Handler = &ValidatingAdmission{}
var _ admission.DecoderInjector = &ValidatingAdmission{}
// Handle implements admission.Handler interface.
// It yields a response to an AdmissionRequest.
func (v *ValidatingAdmission) Handle(ctx context.Context, req admission.Request) admission.Response {
policy := &policyv1alpha1.ClusterPropagationPolicy{}
err := v.decoder.Decode(req, policy)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
klog.V(2).Infof("Validating ClusterPropagationPolicy(%s) for request: %s", policy.Name, req.Operation)
if req.Operation == v1beta1.Update {
oldPolicy := &policyv1alpha1.ClusterPropagationPolicy{}
err := v.decoder.DecodeRaw(req.OldObject, oldPolicy)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if !equality.Semantic.DeepEqual(policy.Spec.ResourceSelectors, oldPolicy.Spec.ResourceSelectors) {
klog.Info(helper.DenyReasonResourceSelectorsModify)
return admission.Denied(helper.DenyReasonResourceSelectorsModify)
}
}
if err := helper.ValidateSpreadConstraint(policy.Spec.Placement.SpreadConstraints); err != nil {
klog.Info(err)
return admission.Denied(err.Error())
}
return admission.Allowed("")
}
// InjectDecoder implements admission.DecoderInjector interface.
// A decoder will be automatically injected.
func (v *ValidatingAdmission) InjectDecoder(d *admission.Decoder) error {
v.decoder = d
return nil
}

View File

@ -9,6 +9,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
"github.com/karmada-io/karmada/pkg/util/helper"
)
// MutatingAdmission mutates API request if necessary.
@ -38,18 +39,7 @@ func (a *MutatingAdmission) Handle(ctx context.Context, req admission.Request) a
}
// Set default spread constraints if both 'SpreadByField' and 'SpreadByLabel' not set.
spreadConstraints := policy.Spec.Placement.SpreadConstraints
for i := range spreadConstraints {
if len(spreadConstraints[i].SpreadByLabel) == 0 && len(spreadConstraints[i].SpreadByField) == 0 {
klog.Infof("Setting default SpreadByField with %s", policyv1alpha1.SpreadByFieldCluster)
spreadConstraints[i].SpreadByField = policyv1alpha1.SpreadByFieldCluster
}
if spreadConstraints[i].MinGroups == 0 {
klog.Infof("Setting default MinGroups to 1")
spreadConstraints[i].MinGroups = 1
}
}
helper.SetDefaultSpreadConstraints(policy.Spec.Placement.SpreadConstraints)
marshaledBytes, err := json.Marshal(policy)
if err != nil {

View File

@ -2,13 +2,15 @@ package propagationpolicy
import (
"context"
"fmt"
"net/http"
"k8s.io/api/admission/v1beta1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
"github.com/karmada-io/karmada/pkg/util/helper"
)
// ValidatingAdmission validates PropagationPolicy object when creating/updating/deleting.
@ -31,20 +33,21 @@ func (v *ValidatingAdmission) Handle(ctx context.Context, req admission.Request)
}
klog.V(2).Infof("Validating PropagationPolicy(%s/%s) for request: %s", policy.Namespace, policy.Name, req.Operation)
// SpreadByField and SpreadByLabel should not co-exist
for _, constraint := range policy.Spec.Placement.SpreadConstraints {
if len(constraint.SpreadByField) > 0 && len(constraint.SpreadByLabel) > 0 {
errMsg := fmt.Sprintf("invalid constraints: SpreadByLabel(%s) should not co-exist with spreadByField(%s)", constraint.SpreadByLabel, constraint.SpreadByField)
klog.Info(errMsg)
return admission.Denied(errMsg)
if req.Operation == v1beta1.Update {
oldPolicy := &policyv1alpha1.PropagationPolicy{}
err := v.decoder.DecodeRaw(req.OldObject, oldPolicy)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if !equality.Semantic.DeepEqual(policy.Spec.ResourceSelectors, oldPolicy.Spec.ResourceSelectors) {
klog.Info(helper.DenyReasonResourceSelectorsModify)
return admission.Denied(helper.DenyReasonResourceSelectorsModify)
}
}
// If MaxGroups provided, it should greater or equal than MinGroups.
if constraint.MaxGroups > 0 && constraint.MaxGroups < constraint.MinGroups {
errMsg := fmt.Sprintf("maxGroups(%d) lower than minGroups(%d) is not allowed", constraint.MaxGroups, constraint.MinGroups)
klog.Info(errMsg)
return admission.Denied(errMsg)
}
if err := helper.ValidateSpreadConstraint(policy.Spec.Placement.SpreadConstraints); err != nil {
klog.Info(err)
return admission.Denied(err.Error())
}
return admission.Allowed("")