package overridemanager import ( "context" "encoding/json" "strings" "github.com/evanphx/json-patch/v5" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1" propagationstrategy "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1" "github.com/karmada-io/karmada/pkg/util" ) // OverrideManager managers override policies operation type OverrideManager interface { ApplyOverridePolicies(rawObj *unstructured.Unstructured, cluster string) error } type overrideOption struct { Op string `json:"op"` Path string `json:"path"` Value interface{} `json:"value,omitempty"` } type overrideManagerImpl struct { client.Client } // New builds an OverrideManager instance. func New(client client.Client) OverrideManager { return &overrideManagerImpl{ Client: client, } } func (o *overrideManagerImpl) ApplyOverridePolicies(rawObj *unstructured.Unstructured, clusterName string) 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 err } // Apply cluster scoped override policies if err := o.applyClusterOverrides(rawObj, clusterObj); err != nil { klog.Errorf("Failed to apply cluster override policies. error: %v", err) return err } // Apply namespace scoped override policies if len(rawObj.GetNamespace()) > 0 { if err := o.applyNamespacedOverrides(rawObj, clusterObj); err != nil { klog.Errorf("Failed to apply namespaced override policies. error: %v", err) return err } } return nil } // applyClusterOverrides will apply overrides according to ClusterOverridePolicy instructions. func (o *overrideManagerImpl) applyClusterOverrides(rawObj *unstructured.Unstructured, cluster *clusterv1alpha1.Cluster) error { // TODO(RainbowMango): implements later after ClusterOverridePolicy get on board. return nil } // applyNamespacedOverrides will apply overrides according to OverridePolicy instructions. func (o *overrideManagerImpl) applyNamespacedOverrides(rawObj *unstructured.Unstructured, cluster *clusterv1alpha1.Cluster) error { // get all namespace-scoped override policies policyList := &propagationstrategy.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 err } if len(policyList.Items) == 0 { return nil } matchedPolicies := o.getMatchedOverridePolicy(policyList.Items, rawObj, cluster) if len(matchedPolicies) == 0 { klog.V(2).Infof("No override policy for resource: %s/%s", rawObj.GetNamespace(), rawObj.GetName()) return nil } var appliedList []string for _, p := range matchedPolicies { klog.Infof("Applying overrides(%s/%s) for %s/%s", p.Namespace, p.Name, rawObj.GetNamespace(), rawObj.GetName()) if err := applyJSONPatch(rawObj, parseJSONPatch(p.Spec.Overriders.Plaintext)); err != nil { return err } appliedList = append(appliedList, p.Name) } util.MergeAnnotation(rawObj, util.AppliedOverrideKey, strings.Join(appliedList, ",")) return nil } func (o *overrideManagerImpl) getMatchedOverridePolicy(policies []propagationstrategy.OverridePolicy, resource *unstructured.Unstructured, cluster *clusterv1alpha1.Cluster) []propagationstrategy.OverridePolicy { // select policy in which at least one resource selector matches target resource. resourceMatches := make([]propagationstrategy.OverridePolicy, 0) for _, policy := range policies { if OverridePolicyMatches(resource, &policy) { resourceMatches = append(resourceMatches, policy) } } // select policy which cluster selector matches target resource. clusterMatches := make([]propagationstrategy.OverridePolicy, 0) for _, policy := range resourceMatches { if util.ClusterMatches(cluster, policy.Spec.TargetCluster) { clusterMatches = append(clusterMatches, policy) } } // select policy in which at least one PlaintextOverrider matches target resource. // TODO(RainbowMango): check if the overrider instructions can be applied to target resource. // TODO(RainbowMango): Sort by policy names. return clusterMatches } func parseJSONPatch(overriders []propagationstrategy.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 } // 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 } // OverridePolicyMatches tells if the override policy should be applied to the resource. func OverridePolicyMatches(resource *unstructured.Unstructured, policy *propagationstrategy.OverridePolicy) bool { for _, rs := range policy.Spec.ResourceSelectors { if util.ResourceMatches(resource, rs) { return true } } return false }