sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/cluster/patches/template.go (about) 1 /* 2 Copyright 2021 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 patches 18 19 import ( 20 "encoding/json" 21 "strconv" 22 23 "github.com/pkg/errors" 24 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/apimachinery/pkg/runtime/schema" 28 "k8s.io/apimachinery/pkg/runtime/serializer" 29 "k8s.io/apimachinery/pkg/types" 30 "k8s.io/apimachinery/pkg/util/uuid" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 33 runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" 34 "sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/patches/variables" 35 ) 36 37 // unstructuredDecoder is used to decode byte arrays into Unstructured objects. 38 var unstructuredDecoder = serializer.NewCodecFactory(nil).UniversalDeserializer() 39 40 // requestItemBuilder builds a GeneratePatchesRequestItem. 41 type requestItemBuilder struct { 42 template *unstructured.Unstructured 43 holder runtimehooksv1.HolderReference 44 } 45 46 // requestTopologyName is used to specify the topology name to match in a GeneratePatchesRequest. 47 type requestTopologyName struct { 48 mdTopologyName string 49 mpTopologyName string 50 } 51 52 // newRequestItemBuilder returns a new requestItemBuilder. 53 func newRequestItemBuilder(template *unstructured.Unstructured) *requestItemBuilder { 54 return &requestItemBuilder{ 55 template: template, 56 } 57 } 58 59 // WithHolder adds holder to the requestItemBuilder. 60 // Note: We pass in gvk explicitly as we can't rely on GVK being set on all objects 61 // (only on Unstructured). 62 func (t *requestItemBuilder) WithHolder(object client.Object, gvk schema.GroupVersionKind, fieldPath string) *requestItemBuilder { 63 t.holder = runtimehooksv1.HolderReference{ 64 APIVersion: gvk.GroupVersion().String(), 65 Kind: gvk.Kind, 66 Namespace: object.GetNamespace(), 67 Name: object.GetName(), 68 FieldPath: fieldPath, 69 } 70 return t 71 } 72 73 // uuidGenerator is defined as a package variable to enable changing it during testing. 74 var uuidGenerator func() types.UID = uuid.NewUUID 75 76 // Build builds a GeneratePatchesRequestItem. 77 func (t *requestItemBuilder) Build() (*runtimehooksv1.GeneratePatchesRequestItem, error) { 78 tpl := &runtimehooksv1.GeneratePatchesRequestItem{ 79 HolderReference: t.holder, 80 UID: uuidGenerator(), 81 } 82 83 jsonObj, err := json.Marshal(t.template) 84 if err != nil { 85 return nil, errors.Wrap(err, "failed to marshal template to JSON") 86 } 87 88 tpl.Object = runtime.RawExtension{ 89 Raw: jsonObj, 90 Object: t.template, 91 } 92 93 return tpl, nil 94 } 95 96 // getTemplateAsUnstructured is a utility func that returns a template matching the holderKind, holderFieldPath 97 // and topologyNames from a GeneratePatchesRequest. 98 func getTemplateAsUnstructured(req *runtimehooksv1.GeneratePatchesRequest, holderKind, holderFieldPath string, topologyNames requestTopologyName) (*unstructured.Unstructured, error) { 99 // Find the requestItem. 100 requestItem := getRequestItem(req, holderKind, holderFieldPath, topologyNames) 101 102 if requestItem == nil { 103 return nil, errors.Errorf("failed to get request item with holder kind %q, holder field path %q, MD topology name %q, and MP topology name %q", holderKind, holderFieldPath, topologyNames.mdTopologyName, topologyNames.mpTopologyName) 104 } 105 106 // Unmarshal the template. 107 template, err := bytesToUnstructured(requestItem.Object.Raw) 108 if err != nil { 109 return nil, errors.Wrapf(err, "failed to convert template to Unstructured") 110 } 111 112 return template, nil 113 } 114 115 // getRequestItemByUID is a utility func that returns a template matching the uid from a GeneratePatchesRequest. 116 func getRequestItemByUID(req *runtimehooksv1.GeneratePatchesRequest, uid types.UID) *runtimehooksv1.GeneratePatchesRequestItem { 117 for i := range req.Items { 118 if req.Items[i].UID == uid { 119 return &req.Items[i] 120 } 121 } 122 return nil 123 } 124 125 // getRequestItem is a utility func that returns a template matching the holderKind, holderFiledPath and topologyNames from a GeneratePatchesRequest. 126 func getRequestItem(req *runtimehooksv1.GeneratePatchesRequest, holderKind, holderFieldPath string, topologyNames requestTopologyName) *runtimehooksv1.GeneratePatchesRequestItem { 127 for _, template := range req.Items { 128 if holderKind != "" && template.HolderReference.Kind != holderKind { 129 continue 130 } 131 if holderFieldPath != "" && template.HolderReference.FieldPath != holderFieldPath { 132 continue 133 } 134 if topologyNames.mdTopologyName != "" { 135 templateVariables := toMap(template.Variables) 136 v, err := variables.GetVariableValue(templateVariables, "builtin.machineDeployment.topologyName") 137 if err != nil || string(v.Raw) != strconv.Quote(topologyNames.mdTopologyName) { 138 continue 139 } 140 } 141 if topologyNames.mpTopologyName != "" { 142 templateVariables := toMap(template.Variables) 143 v, err := variables.GetVariableValue(templateVariables, "builtin.machinePool.topologyName") 144 if err != nil || string(v.Raw) != strconv.Quote(topologyNames.mpTopologyName) { 145 continue 146 } 147 } 148 149 return &template 150 } 151 return nil 152 } 153 154 // bytesToUnstructured provides a utility method that converts a (JSON) byte array into an Unstructured object. 155 func bytesToUnstructured(b []byte) (*unstructured.Unstructured, error) { 156 // Unmarshal the JSON. 157 u := &unstructured.Unstructured{} 158 if _, _, err := unstructuredDecoder.Decode(b, nil, u); err != nil { 159 return nil, errors.Wrap(err, "failed to unmarshal object from json") 160 } 161 162 return u, nil 163 } 164 165 // toMap converts a list of Variables to a map of JSON (name is the map key). 166 func toMap(variables []runtimehooksv1.Variable) map[string]apiextensionsv1.JSON { 167 variablesMap := map[string]apiextensionsv1.JSON{} 168 169 for i := range variables { 170 variablesMap[variables[i].Name] = variables[i].Value 171 } 172 return variablesMap 173 }