github.com/oam-dev/kubevela@v1.9.11/pkg/resourcekeeper/statekeep.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package resourcekeeper
    18  
    19  import (
    20  	"context"
    21  
    22  	"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
    23  	"github.com/kubevela/pkg/util/maps"
    24  	"github.com/kubevela/pkg/util/slices"
    25  	"github.com/pkg/errors"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/types"
    28  
    29  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    30  
    31  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
    32  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    33  	"github.com/oam-dev/kubevela/pkg/auth"
    34  	"github.com/oam-dev/kubevela/pkg/multicluster"
    35  	"github.com/oam-dev/kubevela/pkg/utils/apply"
    36  	velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors"
    37  )
    38  
    39  // StateKeep run this function to keep resources up-to-date
    40  func (h *resourceKeeper) StateKeep(ctx context.Context) error {
    41  	if h.applyOncePolicy != nil && h.applyOncePolicy.Enable && h.applyOncePolicy.Rules == nil {
    42  		return nil
    43  	}
    44  	ctx = auth.ContextWithUserInfo(ctx, h.app)
    45  	mrs := make(map[string]v1beta1.ManagedResource)
    46  	belongs := make(map[string]*v1beta1.ResourceTracker)
    47  	for _, rt := range []*v1beta1.ResourceTracker{h._currentRT, h._rootRT} {
    48  		if rt != nil && rt.GetDeletionTimestamp() == nil {
    49  			for _, mr := range rt.Spec.ManagedResources {
    50  				key := mr.ResourceKey()
    51  				mrs[key] = mr
    52  				belongs[key] = rt
    53  			}
    54  		}
    55  	}
    56  	errs := slices.ParMap(maps.Values(mrs), func(mr v1beta1.ManagedResource) error {
    57  		rt := belongs[mr.ResourceKey()]
    58  		entry := h.cache.get(ctx, mr)
    59  		if entry.err != nil {
    60  			return entry.err
    61  		}
    62  		if mr.Deleted {
    63  			if entry.exists && entry.obj != nil && entry.obj.GetDeletionTimestamp() == nil {
    64  				deleteCtx := multicluster.ContextWithClusterName(ctx, mr.Cluster)
    65  				if err := h.Client.Delete(deleteCtx, entry.obj); err != nil {
    66  					return errors.Wrapf(err, "failed to delete outdated resource %s in resourcetracker %s", mr.ResourceKey(), rt.Name)
    67  				}
    68  			}
    69  		} else {
    70  			if mr.Data == nil || mr.Data.Raw == nil {
    71  				// no state-keep
    72  				return nil
    73  			}
    74  			manifest, err := mr.ToUnstructuredWithData()
    75  			if err != nil {
    76  				return errors.Wrapf(err, "failed to decode resource %s from resourcetracker", mr.ResourceKey())
    77  			}
    78  			applyCtx := multicluster.ContextWithClusterName(ctx, mr.Cluster)
    79  			manifest, err = ApplyStrategies(applyCtx, h, manifest, v1alpha1.ApplyOnceStrategyOnAppStateKeep)
    80  			if err != nil {
    81  				return errors.Wrapf(err, "failed to apply once resource %s from resourcetracker %s", mr.ResourceKey(), rt.Name)
    82  			}
    83  			ao := []apply.ApplyOption{apply.MustBeControlledByApp(h.app)}
    84  			if h.isShared(manifest) {
    85  				ao = append([]apply.ApplyOption{apply.SharedByApp(h.app)}, ao...)
    86  			}
    87  			if h.isReadOnly(manifest) {
    88  				ao = append([]apply.ApplyOption{apply.ReadOnly()}, ao...)
    89  			}
    90  			if h.canTakeOver(manifest) {
    91  				ao = append([]apply.ApplyOption{apply.TakeOver()}, ao...)
    92  			}
    93  			if strategy := h.getUpdateStrategy(manifest); strategy != nil {
    94  				ao = append([]apply.ApplyOption{apply.WithUpdateStrategy(*strategy)}, ao...)
    95  			}
    96  			if err = h.applicator.Apply(applyCtx, manifest, ao...); err != nil {
    97  				return errors.Wrapf(err, "failed to re-apply resource %s from resourcetracker %s", mr.ResourceKey(), rt.Name)
    98  			}
    99  		}
   100  		return nil
   101  	}, slices.Parallelism(MaxDispatchConcurrent))
   102  	return velaerrors.AggregateErrors(errs)
   103  }
   104  
   105  // ApplyStrategies will generate manifest with applyOnceStrategy
   106  func ApplyStrategies(ctx context.Context, h *resourceKeeper, manifest *unstructured.Unstructured, matchedAffectStage v1alpha1.ApplyOnceAffectStrategy) (*unstructured.Unstructured, error) {
   107  	if h.applyOncePolicy == nil {
   108  		return manifest, nil
   109  	}
   110  	strategy := h.applyOncePolicy.FindStrategy(manifest)
   111  	if strategy != nil {
   112  		affectStage := strategy.ApplyOnceAffectStrategy
   113  		if shouldMerge(affectStage, matchedAffectStage) {
   114  			un := new(unstructured.Unstructured)
   115  			un.SetAPIVersion(manifest.GetAPIVersion())
   116  			un.SetKind(manifest.GetKind())
   117  			err := h.Get(ctx, types.NamespacedName{Name: manifest.GetName(), Namespace: manifest.GetNamespace()}, un)
   118  			if err != nil {
   119  				if kerrors.IsNotFound(err) {
   120  					return manifest, nil
   121  				}
   122  				return nil, err
   123  			}
   124  			return mergeValue(strategy.Path, manifest, un)
   125  		}
   126  
   127  	}
   128  	return manifest, nil
   129  }
   130  
   131  func shouldMerge(affectStage v1alpha1.ApplyOnceAffectStrategy, matchedAffectType v1alpha1.ApplyOnceAffectStrategy) bool {
   132  	return affectStage == "" || affectStage == v1alpha1.ApplyOnceStrategyAlways || affectStage == matchedAffectType
   133  }
   134  
   135  func mergeValue(paths []string, manifest *unstructured.Unstructured, un *unstructured.Unstructured) (*unstructured.Unstructured, error) {
   136  	for _, path := range paths {
   137  		if path == "*" {
   138  			manifest = un.DeepCopy()
   139  			break
   140  		}
   141  		value, err := fieldpath.Pave(un.UnstructuredContent()).GetValue(path)
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  		err = fieldpath.Pave(manifest.UnstructuredContent()).SetValue(path, value)
   146  		if err != nil {
   147  			return nil, err
   148  		}
   149  	}
   150  	return manifest, nil
   151  }