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 }