350 lines
13 KiB
Go
350 lines
13 KiB
Go
package overridemanager
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"sort"
|
|
|
|
jsonpatch "github.com/evanphx/json-patch/v5"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/client-go/tools/record"
|
|
"k8s.io/klog/v2"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
|
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
|
|
"github.com/karmada-io/karmada/pkg/events"
|
|
"github.com/karmada-io/karmada/pkg/util"
|
|
)
|
|
|
|
const (
|
|
// OverrideManagerName is the manager name that will be used when reporting events.
|
|
OverrideManagerName = "override-manager"
|
|
)
|
|
|
|
// OverrideManager managers override policies operation
|
|
type OverrideManager interface {
|
|
// ApplyOverridePolicies overrides the object if one or more override policies exist and matches the target cluster.
|
|
// For cluster scoped resource:
|
|
// - Apply ClusterOverridePolicy by policies name in ascending
|
|
// For namespaced scoped resource, apply order is:
|
|
// - First apply ClusterOverridePolicy;
|
|
// - Then apply OverridePolicy;
|
|
ApplyOverridePolicies(rawObj *unstructured.Unstructured, cluster string) (appliedClusterPolicies *AppliedOverrides, appliedNamespacedPolicies *AppliedOverrides, err error)
|
|
}
|
|
|
|
// GeneralOverridePolicy is an abstract object of ClusterOverridePolicy and OverridePolicy
|
|
type GeneralOverridePolicy interface {
|
|
// GetName returns the name of OverridePolicy
|
|
GetName() string
|
|
// GetNamespace returns the namespace of OverridePolicy
|
|
GetNamespace() string
|
|
// GetOverrideSpec returns the OverrideSpec of OverridePolicy
|
|
GetOverrideSpec() policyv1alpha1.OverrideSpec
|
|
}
|
|
|
|
// overrideOption define the JSONPatch operator
|
|
type overrideOption struct {
|
|
Op string `json:"op"`
|
|
Path string `json:"path"`
|
|
Value interface{} `json:"value,omitempty"`
|
|
}
|
|
|
|
type policyOverriders struct {
|
|
name string
|
|
namespace string
|
|
overriders policyv1alpha1.Overriders
|
|
}
|
|
|
|
type overrideManagerImpl struct {
|
|
client.Client
|
|
record.EventRecorder
|
|
}
|
|
|
|
// New builds an OverrideManager instance.
|
|
func New(client client.Client, eventRecorder record.EventRecorder) OverrideManager {
|
|
return &overrideManagerImpl{
|
|
Client: client,
|
|
EventRecorder: eventRecorder,
|
|
}
|
|
}
|
|
|
|
func (o *overrideManagerImpl) ApplyOverridePolicies(rawObj *unstructured.Unstructured, clusterName string) (*AppliedOverrides, *AppliedOverrides, error) {
|
|
clusterObj := &clusterv1alpha1.Cluster{}
|
|
if err := o.Client.Get(context.TODO(), client.ObjectKey{Name: clusterName}, clusterObj); err != nil {
|
|
klog.Errorf("Failed to get member cluster: %s, error: %v", clusterName, err)
|
|
return nil, nil, err
|
|
}
|
|
|
|
var appliedClusterOverrides *AppliedOverrides
|
|
var appliedNamespacedOverrides *AppliedOverrides
|
|
var err error
|
|
|
|
// Apply cluster scoped override policies
|
|
appliedClusterOverrides, err = o.applyClusterOverrides(rawObj, clusterObj)
|
|
if err != nil {
|
|
klog.Errorf("Failed to apply cluster override policies. error: %v", err)
|
|
return nil, nil, err
|
|
}
|
|
|
|
// For namespace scoped resources, should apply override policies under the same namespace.
|
|
// No matter the resources propagated by ClusterPropagationPolicy or PropagationPolicy.
|
|
if len(rawObj.GetNamespace()) > 0 {
|
|
// Apply namespace scoped override policies
|
|
appliedNamespacedOverrides, err = o.applyNamespacedOverrides(rawObj, clusterObj)
|
|
if err != nil {
|
|
klog.Errorf("Failed to apply namespaced override policies. error: %v", err)
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
return appliedClusterOverrides, appliedNamespacedOverrides, nil
|
|
}
|
|
|
|
// applyClusterOverrides will apply overrides according to ClusterOverridePolicy instructions.
|
|
func (o *overrideManagerImpl) applyClusterOverrides(rawObj *unstructured.Unstructured, cluster *clusterv1alpha1.Cluster) (*AppliedOverrides, error) {
|
|
// get all cluster-scoped override policies
|
|
policyList := &policyv1alpha1.ClusterOverridePolicyList{}
|
|
if err := o.Client.List(context.TODO(), policyList, &client.ListOptions{}); err != nil {
|
|
klog.Errorf("Failed to list cluster override policies, error: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
if len(policyList.Items) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
items := make([]GeneralOverridePolicy, 0, len(policyList.Items))
|
|
for i := range policyList.Items {
|
|
items = append(items, &policyList.Items[i])
|
|
}
|
|
matchingPolicyOverriders := o.getOverridersFromOverridePolicies(items, rawObj, cluster)
|
|
if len(matchingPolicyOverriders) == 0 {
|
|
klog.V(2).Infof("No cluster override policy for resource: %s/%s", rawObj.GetNamespace(), rawObj.GetName())
|
|
return nil, nil
|
|
}
|
|
|
|
appliedList := &AppliedOverrides{}
|
|
for _, p := range matchingPolicyOverriders {
|
|
if err := applyPolicyOverriders(rawObj, p.overriders); err != nil {
|
|
klog.Errorf("Failed to apply cluster overrides(%s) for resource(%s/%s), error: %v", p.name, rawObj.GetNamespace(), rawObj.GetName(), err)
|
|
o.EventRecorder.Eventf(rawObj, corev1.EventTypeWarning, events.EventReasonApplyOverridePolicyFailed, "Apply cluster override policy(%s) for cluster(%s) failed.", p.name, cluster.Name)
|
|
return nil, err
|
|
}
|
|
klog.V(2).Infof("Applied cluster overrides(%s) for resource(%s/%s)", p.name, rawObj.GetNamespace(), rawObj.GetName())
|
|
o.EventRecorder.Eventf(rawObj, corev1.EventTypeNormal, events.EventReasonApplyOverridePolicySucceed, "Apply cluster override policy(%s) for cluster(%s) succeed.", p.name, cluster.Name)
|
|
appliedList.Add(p.name, p.overriders)
|
|
}
|
|
|
|
return appliedList, nil
|
|
}
|
|
|
|
// applyNamespacedOverrides will apply overrides according to OverridePolicy instructions.
|
|
func (o *overrideManagerImpl) applyNamespacedOverrides(rawObj *unstructured.Unstructured, cluster *clusterv1alpha1.Cluster) (*AppliedOverrides, error) {
|
|
// get all namespace-scoped override policies
|
|
policyList := &policyv1alpha1.OverridePolicyList{}
|
|
if err := o.Client.List(context.TODO(), policyList, &client.ListOptions{Namespace: rawObj.GetNamespace()}); err != nil {
|
|
klog.Errorf("Failed to list override policies from namespace: %s, error: %v", rawObj.GetNamespace(), err)
|
|
return nil, err
|
|
}
|
|
|
|
if len(policyList.Items) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
items := make([]GeneralOverridePolicy, 0, len(policyList.Items))
|
|
for i := range policyList.Items {
|
|
items = append(items, &policyList.Items[i])
|
|
}
|
|
matchingPolicyOverriders := o.getOverridersFromOverridePolicies(items, rawObj, cluster)
|
|
if len(matchingPolicyOverriders) == 0 {
|
|
klog.V(2).Infof("No override policy for resource(%s/%s)", rawObj.GetNamespace(), rawObj.GetName())
|
|
return nil, nil
|
|
}
|
|
|
|
appliedList := &AppliedOverrides{}
|
|
for _, p := range matchingPolicyOverriders {
|
|
if err := applyPolicyOverriders(rawObj, p.overriders); err != nil {
|
|
klog.Errorf("Failed to apply overrides(%s/%s) for resource(%s/%s), error: %v", p.namespace, p.name, rawObj.GetNamespace(), rawObj.GetName(), err)
|
|
o.EventRecorder.Eventf(rawObj, corev1.EventTypeWarning, events.EventReasonApplyOverridePolicyFailed, "Apply override policy(%s/%s) for cluster(%s) failed.", p.namespace, p.name, cluster.Name)
|
|
return nil, err
|
|
}
|
|
klog.V(2).Infof("Applied overrides(%s/%s) for resource(%s/%s)", p.namespace, p.name, rawObj.GetNamespace(), rawObj.GetName())
|
|
o.EventRecorder.Eventf(rawObj, corev1.EventTypeNormal, events.EventReasonApplyOverridePolicySucceed, "Apply override policy(%s/%s) for cluster(%s) succeed.", p.namespace, p.name, cluster.Name)
|
|
appliedList.Add(p.name, p.overriders)
|
|
}
|
|
|
|
return appliedList, nil
|
|
}
|
|
|
|
func (o *overrideManagerImpl) getOverridersFromOverridePolicies(policies []GeneralOverridePolicy, resource *unstructured.Unstructured, cluster *clusterv1alpha1.Cluster) []policyOverriders {
|
|
resourceMatchingPolicies := make([]GeneralOverridePolicy, 0)
|
|
for _, policy := range policies {
|
|
if len(policy.GetOverrideSpec().ResourceSelectors) == 0 {
|
|
resourceMatchingPolicies = append(resourceMatchingPolicies, policy)
|
|
continue
|
|
}
|
|
|
|
if util.ResourceMatchSelectors(resource, policy.GetOverrideSpec().ResourceSelectors...) {
|
|
resourceMatchingPolicies = append(resourceMatchingPolicies, policy)
|
|
}
|
|
}
|
|
sort.SliceStable(resourceMatchingPolicies, func(i, j int) bool {
|
|
implicitPriorityI := util.ResourceMatchSelectorsPriority(resource, resourceMatchingPolicies[i].GetOverrideSpec().ResourceSelectors...)
|
|
if len(resourceMatchingPolicies[i].GetOverrideSpec().ResourceSelectors) == 0 {
|
|
implicitPriorityI = util.PriorityMatchAll
|
|
}
|
|
implicitPriorityJ := util.ResourceMatchSelectorsPriority(resource, resourceMatchingPolicies[j].GetOverrideSpec().ResourceSelectors...)
|
|
if len(resourceMatchingPolicies[j].GetOverrideSpec().ResourceSelectors) == 0 {
|
|
implicitPriorityJ = util.PriorityMatchAll
|
|
}
|
|
if implicitPriorityI != implicitPriorityJ {
|
|
return implicitPriorityI < implicitPriorityJ
|
|
}
|
|
return resourceMatchingPolicies[i].GetName() < resourceMatchingPolicies[j].GetName()
|
|
})
|
|
clusterMatchingPolicyOverriders := make([]policyOverriders, 0)
|
|
for _, policy := range resourceMatchingPolicies {
|
|
overrideRules := policy.GetOverrideSpec().OverrideRules
|
|
// Since the tuple of '.spec.TargetCluster' and '.spec.Overriders' can not co-exist with '.spec.OverrideRules'
|
|
// (guaranteed by webhook), so we only look '.spec.OverrideRules' here.
|
|
if len(overrideRules) == 0 {
|
|
overrideRules = []policyv1alpha1.RuleWithCluster{
|
|
{
|
|
//nolint:staticcheck
|
|
// disable `deprecation` check for backward compatibility.
|
|
TargetCluster: policy.GetOverrideSpec().TargetCluster,
|
|
//nolint:staticcheck
|
|
// disable `deprecation` check for backward compatibility.
|
|
Overriders: policy.GetOverrideSpec().Overriders,
|
|
},
|
|
}
|
|
}
|
|
for _, rule := range overrideRules {
|
|
if rule.TargetCluster == nil || (rule.TargetCluster != nil && util.ClusterMatches(cluster, *rule.TargetCluster)) {
|
|
clusterMatchingPolicyOverriders = append(clusterMatchingPolicyOverriders, policyOverriders{
|
|
name: policy.GetName(),
|
|
namespace: policy.GetNamespace(),
|
|
overriders: rule.Overriders,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// select policy in which at least one PlaintextOverrider matches target resource.
|
|
// TODO(RainbowMango): check if the overrider instructions can be applied to target resource.
|
|
|
|
return clusterMatchingPolicyOverriders
|
|
}
|
|
|
|
// applyJSONPatch applies the override on to the given unstructured object.
|
|
func applyJSONPatch(obj *unstructured.Unstructured, overrides []overrideOption) error {
|
|
jsonPatchBytes, err := json.Marshal(overrides)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
patch, err := jsonpatch.DecodePatch(jsonPatchBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
objectJSONBytes, err := obj.MarshalJSON()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
patchedObjectJSONBytes, err := patch.Apply(objectJSONBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = obj.UnmarshalJSON(patchedObjectJSONBytes)
|
|
return err
|
|
}
|
|
|
|
// applyPolicyOverriders applies OverridePolicy/ClusterOverridePolicy overriders to target object
|
|
func applyPolicyOverriders(rawObj *unstructured.Unstructured, overriders policyv1alpha1.Overriders) error {
|
|
err := applyImageOverriders(rawObj, overriders.ImageOverrider)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// patch command
|
|
if err := applyCommandOverriders(rawObj, overriders.CommandOverrider); err != nil {
|
|
return err
|
|
}
|
|
// patch args
|
|
if err := applyArgsOverriders(rawObj, overriders.ArgsOverrider); err != nil {
|
|
return err
|
|
}
|
|
if err := applyLabelsOverriders(rawObj, overriders.LabelsOverrider); err != nil {
|
|
return err
|
|
}
|
|
if err := applyAnnotationsOverriders(rawObj, overriders.AnnotationsOverrider); err != nil {
|
|
return err
|
|
}
|
|
return applyJSONPatch(rawObj, parseJSONPatchesByPlaintext(overriders.Plaintext))
|
|
}
|
|
|
|
func applyImageOverriders(rawObj *unstructured.Unstructured, imageOverriders []policyv1alpha1.ImageOverrider) error {
|
|
for index := range imageOverriders {
|
|
patches, err := buildPatches(rawObj, &imageOverriders[index])
|
|
if err != nil {
|
|
klog.Errorf("Build patches with imageOverrides err: %v", err)
|
|
return err
|
|
}
|
|
|
|
klog.V(4).Infof("Parsed JSON patches by imageOverriders(%+v): %+v", imageOverriders[index], patches)
|
|
if err = applyJSONPatch(rawObj, patches); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func applyCommandOverriders(rawObj *unstructured.Unstructured, commandOverriders []policyv1alpha1.CommandArgsOverrider) error {
|
|
for index := range commandOverriders {
|
|
patches, err := buildCommandArgsPatches(CommandString, rawObj, &commandOverriders[index])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
klog.V(4).Infof("Parsed JSON patches by commandOverriders(%+v): %+v", commandOverriders[index], patches)
|
|
if err = applyJSONPatch(rawObj, patches); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func applyArgsOverriders(rawObj *unstructured.Unstructured, argsOverriders []policyv1alpha1.CommandArgsOverrider) error {
|
|
for index := range argsOverriders {
|
|
patches, err := buildCommandArgsPatches(ArgsString, rawObj, &argsOverriders[index])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
klog.V(4).Infof("Parsed JSON patches by argsOverriders(%+v): %+v", argsOverriders[index], patches)
|
|
if err = applyJSONPatch(rawObj, patches); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseJSONPatchesByPlaintext(overriders []policyv1alpha1.PlaintextOverrider) []overrideOption {
|
|
patches := make([]overrideOption, 0, len(overriders))
|
|
for i := range overriders {
|
|
patches = append(patches, overrideOption{
|
|
Op: string(overriders[i].Operator),
|
|
Path: overriders[i].Path,
|
|
Value: overriders[i].Value,
|
|
})
|
|
}
|
|
return patches
|
|
}
|