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