sigs.k8s.io/cluster-api@v1.7.1/exp/addons/internal/controllers/clusterresourceset_scope.go (about) 1 /* 2 Copyright 2022 The Kubernetes 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 controllers 18 19 import ( 20 "context" 21 22 "github.com/pkg/errors" 23 apierrors "k8s.io/apimachinery/pkg/api/errors" 24 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 25 kerrors "k8s.io/apimachinery/pkg/util/errors" 26 "k8s.io/klog/v2" 27 "sigs.k8s.io/controller-runtime/pkg/client" 28 29 addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1" 30 ) 31 32 // resourceReconcileScope contains the scope for a CRS's resource 33 // reconciliation request. 34 type resourceReconcileScope interface { 35 // needsApply determines if a resource needs to be applied to the target cluster 36 // based on the strategy. 37 needsApply() bool 38 // apply reconciles all objects defined by the resource following the proper strategy for the CRS. 39 apply(ctx context.Context, c client.Client) error 40 // hash returns a computed hash of the defined objects in the resource. It is consistent 41 // between runs. 42 hash() string 43 } 44 45 func reconcileScopeForResource( 46 crs *addonsv1.ClusterResourceSet, 47 resourceRef addonsv1.ResourceRef, 48 resourceSetBinding *addonsv1.ResourceSetBinding, 49 resource *unstructured.Unstructured, 50 ) (resourceReconcileScope, error) { 51 normalizedData, err := normalizeData(resource) 52 if err != nil { 53 return nil, err 54 } 55 56 objs, err := objsFromYamlData(normalizedData) 57 if err != nil { 58 return nil, err 59 } 60 61 return newResourceReconcileScope(crs, resourceRef, resourceSetBinding, normalizedData, objs), nil 62 } 63 64 func newResourceReconcileScope( 65 clusterResourceSet *addonsv1.ClusterResourceSet, 66 resourceRef addonsv1.ResourceRef, 67 resourceSetBinding *addonsv1.ResourceSetBinding, 68 normalizedData [][]byte, 69 objs []unstructured.Unstructured, 70 ) resourceReconcileScope { 71 base := baseResourceReconcileScope{ 72 clusterResourceSet: clusterResourceSet, 73 resourceRef: resourceRef, 74 resourceSetBinding: resourceSetBinding, 75 data: normalizedData, 76 normalizedObjs: objs, 77 computedHash: computeHash(normalizedData), 78 } 79 80 switch addonsv1.ClusterResourceSetStrategy(clusterResourceSet.Spec.Strategy) { 81 case addonsv1.ClusterResourceSetStrategyApplyOnce: 82 return &reconcileApplyOnceScope{base} 83 case addonsv1.ClusterResourceSetStrategyReconcile: 84 return &reconcileStrategyScope{base} 85 default: 86 return nil 87 } 88 } 89 90 type baseResourceReconcileScope struct { 91 clusterResourceSet *addonsv1.ClusterResourceSet 92 resourceRef addonsv1.ResourceRef 93 resourceSetBinding *addonsv1.ResourceSetBinding 94 normalizedObjs []unstructured.Unstructured 95 data [][]byte 96 computedHash string 97 } 98 99 func (b baseResourceReconcileScope) objs() []unstructured.Unstructured { 100 return b.normalizedObjs 101 } 102 103 func (b baseResourceReconcileScope) hash() string { 104 return b.computedHash 105 } 106 107 type reconcileStrategyScope struct { 108 baseResourceReconcileScope 109 } 110 111 func (r *reconcileStrategyScope) needsApply() bool { 112 resourceBinding := r.resourceSetBinding.GetResource(r.resourceRef) 113 114 return resourceBinding == nil || !resourceBinding.Applied || resourceBinding.Hash != r.computedHash 115 } 116 117 func (r *reconcileStrategyScope) apply(ctx context.Context, c client.Client) error { 118 return apply(ctx, c, r.applyObj, r.objs()) 119 } 120 121 func (r *reconcileStrategyScope) applyObj(ctx context.Context, c client.Client, obj *unstructured.Unstructured) error { 122 currentObj := &unstructured.Unstructured{} 123 currentObj.SetAPIVersion(obj.GetAPIVersion()) 124 currentObj.SetKind(obj.GetKind()) 125 err := c.Get(ctx, client.ObjectKeyFromObject(obj), currentObj) 126 if apierrors.IsNotFound(err) { 127 return createUnstructured(ctx, c, obj) 128 } 129 if err != nil { 130 return errors.Wrapf( 131 err, 132 "reading object %s %s", 133 obj.GroupVersionKind(), 134 klog.KObj(obj), 135 ) 136 } 137 138 patch := client.MergeFrom(currentObj.DeepCopy()) 139 obj.SetResourceVersion(currentObj.GetResourceVersion()) 140 if err = c.Patch(ctx, obj, patch); err != nil { 141 return errors.Wrapf( 142 err, 143 "patching object %s %s", 144 obj.GroupVersionKind(), 145 klog.KObj(obj), 146 ) 147 } 148 149 return nil 150 } 151 152 type reconcileApplyOnceScope struct { 153 baseResourceReconcileScope 154 } 155 156 func (r *reconcileApplyOnceScope) needsApply() bool { 157 return !r.resourceSetBinding.IsApplied(r.resourceRef) 158 } 159 160 func (r *reconcileApplyOnceScope) apply(ctx context.Context, c client.Client) error { 161 return apply(ctx, c, r.applyObj, r.objs()) 162 } 163 164 func (r *reconcileApplyOnceScope) applyObj(ctx context.Context, c client.Client, obj *unstructured.Unstructured) error { 165 // The create call is idempotent, so if the object already exists 166 // then do not consider it to be an error. 167 if err := createUnstructured(ctx, c, obj); err != nil && !apierrors.IsAlreadyExists(err) { 168 return err 169 } 170 171 return nil 172 } 173 174 type applyObj func(ctx context.Context, c client.Client, obj *unstructured.Unstructured) error 175 176 // apply reconciles unstructured objects using applyObj and aggreates the error if present. 177 func apply(ctx context.Context, c client.Client, applyObj applyObj, objs []unstructured.Unstructured) error { 178 errList := []error{} 179 for i := range objs { 180 if err := applyObj(ctx, c, &objs[i]); err != nil { 181 errList = append(errList, err) 182 } 183 } 184 185 return kerrors.NewAggregate(errList) 186 }