sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/cluster/structuredmerge/serversidepathhelper.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 structuredmerge 18 19 import ( 20 "context" 21 22 "github.com/pkg/errors" 23 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 24 ctrl "sigs.k8s.io/controller-runtime" 25 "sigs.k8s.io/controller-runtime/pkg/client" 26 27 "sigs.k8s.io/cluster-api/internal/util/ssa" 28 "sigs.k8s.io/cluster-api/util" 29 ) 30 31 // TopologyManagerName is the manager name in managed fields for the topology controller. 32 const TopologyManagerName = "capi-topology" 33 34 type serverSidePatchHelper struct { 35 client client.Client 36 modified *unstructured.Unstructured 37 hasChanges bool 38 hasSpecChanges bool 39 } 40 41 // NewServerSidePatchHelper returns a new PatchHelper using server side apply. 42 func NewServerSidePatchHelper(ctx context.Context, original, modified client.Object, c client.Client, ssaCache ssa.Cache, opts ...HelperOption) (PatchHelper, error) { 43 // Create helperOptions for filtering the original and modified objects to the desired intent. 44 helperOptions := newHelperOptions(modified, opts...) 45 46 // If required, convert the original and modified objects to unstructured and filter out all the information 47 // not relevant for the topology controller. 48 49 var originalUnstructured *unstructured.Unstructured 50 if !util.IsNil(original) { 51 originalUnstructured = &unstructured.Unstructured{} 52 switch original.(type) { 53 case *unstructured.Unstructured: 54 originalUnstructured = original.DeepCopyObject().(*unstructured.Unstructured) 55 default: 56 if err := c.Scheme().Convert(original, originalUnstructured, nil); err != nil { 57 return nil, errors.Wrap(err, "failed to convert original object to Unstructured") 58 } 59 } 60 } 61 62 modifiedUnstructured := &unstructured.Unstructured{} 63 switch modified.(type) { 64 case *unstructured.Unstructured: 65 modifiedUnstructured = modified.DeepCopyObject().(*unstructured.Unstructured) 66 default: 67 if err := c.Scheme().Convert(modified, modifiedUnstructured, nil); err != nil { 68 return nil, errors.Wrap(err, "failed to convert modified object to Unstructured") 69 } 70 } 71 72 // Filter the modifiedUnstructured object to only contain changes intendet to be done. 73 // The originalUnstructured object will be filtered in dryRunSSAPatch using other options. 74 ssa.FilterObject(modifiedUnstructured, &helperOptions.FilterObjectInput) 75 76 // Carry over uid to match the intent to: 77 // * create (uid==""): 78 // * if object doesn't exist => create 79 // * if object already exists => update 80 // * update (uid!=""): 81 // * if object doesn't exist => fail 82 // * if object already exists => update 83 // * This allows us to enforce that an update doesn't create an object which has been deleted. 84 modifiedUnstructured.SetUID("") 85 if originalUnstructured != nil { 86 modifiedUnstructured.SetUID(originalUnstructured.GetUID()) 87 } 88 89 // Determine if the intent defined in the modified object is going to trigger 90 // an actual change when running server side apply, and if this change might impact the object spec or not. 91 var hasChanges, hasSpecChanges bool 92 switch { 93 case util.IsNil(original): 94 hasChanges, hasSpecChanges = true, true 95 default: 96 var err error 97 hasChanges, hasSpecChanges, err = dryRunSSAPatch(ctx, &dryRunSSAPatchInput{ 98 client: c, 99 ssaCache: ssaCache, 100 originalUnstructured: originalUnstructured, 101 modifiedUnstructured: modifiedUnstructured.DeepCopy(), 102 helperOptions: helperOptions, 103 }) 104 if err != nil { 105 return nil, err 106 } 107 } 108 109 return &serverSidePatchHelper{ 110 client: c, 111 modified: modifiedUnstructured, 112 hasChanges: hasChanges, 113 hasSpecChanges: hasSpecChanges, 114 }, nil 115 } 116 117 // HasSpecChanges return true if the patch has changes to the spec field. 118 func (h *serverSidePatchHelper) HasSpecChanges() bool { 119 return h.hasSpecChanges 120 } 121 122 // HasChanges return true if the patch has changes. 123 func (h *serverSidePatchHelper) HasChanges() bool { 124 return h.hasChanges 125 } 126 127 // Patch will server side apply the current intent (the modified object. 128 func (h *serverSidePatchHelper) Patch(ctx context.Context) error { 129 if !h.HasChanges() { 130 return nil 131 } 132 133 log := ctrl.LoggerFrom(ctx) 134 log.V(5).Info("Patching object", "Intent", h.modified) 135 136 options := []client.PatchOption{ 137 client.FieldOwner(TopologyManagerName), 138 // NOTE: we are using force ownership so in case of conflicts the topology controller 139 // overwrite values and become sole manager. 140 client.ForceOwnership, 141 } 142 return h.client.Patch(ctx, h.modified, client.Apply, options...) 143 }