sigs.k8s.io/cluster-api@v1.7.1/exp/runtime/topologymutation/variables.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 topologymutation 18 19 import ( 20 "encoding/json" 21 "strconv" 22 "strings" 23 24 "github.com/pkg/errors" 25 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 26 27 runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" 28 patchvariables "sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/patches/variables" 29 ) 30 31 // TODO: Add func for validating received variables are of the expected types. 32 33 // GetVariable get the variable value. 34 func GetVariable(templateVariables map[string]apiextensionsv1.JSON, variableName string) (*apiextensionsv1.JSON, error) { 35 value, err := patchvariables.GetVariableValue(templateVariables, variableName) 36 if err != nil { 37 return nil, err 38 } 39 return value, nil 40 } 41 42 // GetStringVariable get the variable value as a string. 43 func GetStringVariable(templateVariables map[string]apiextensionsv1.JSON, variableName string) (string, error) { 44 value, err := GetVariable(templateVariables, variableName) 45 if err != nil { 46 return "", err 47 } 48 49 // Unquote the JSON string. 50 stringValue, err := strconv.Unquote(string(value.Raw)) 51 if err != nil { 52 return "", err 53 } 54 return stringValue, nil 55 } 56 57 // GetBoolVariable get the value as a bool. 58 func GetBoolVariable(templateVariables map[string]apiextensionsv1.JSON, variableName string) (bool, error) { 59 value, err := GetVariable(templateVariables, variableName) 60 if err != nil { 61 return false, err 62 } 63 64 // Parse the JSON bool. 65 boolValue, err := strconv.ParseBool(string(value.Raw)) 66 if err != nil { 67 return false, err 68 } 69 return boolValue, nil 70 } 71 72 // GetObjectVariableInto gets variable's string value then unmarshal it into 73 // object passed from 'into'. 74 func GetObjectVariableInto(templateVariables map[string]apiextensionsv1.JSON, variableName string, into interface{}) error { 75 value, err := GetVariable(templateVariables, variableName) 76 if err != nil { 77 return err 78 } 79 80 if err := json.Unmarshal(sanitizeJSON(value.Raw), into); err != nil { 81 return errors.Wrapf(err, "failed to unmarshal variable json %q into %q", string(value.Raw), into) 82 } 83 84 return nil 85 } 86 87 func sanitizeJSON(input []byte) (output []byte) { 88 output = []byte(strings.ReplaceAll(string(input), "\\", "")) 89 return output 90 } 91 92 // ToMap converts a list of Variables to a map of apiextensionsv1.JSON (name is the map key). 93 // This is usually used to convert the Variables in a GeneratePatchesRequestItem into a format 94 // that is used by MergeVariableMaps. 95 func ToMap(variables []runtimehooksv1.Variable) map[string]apiextensionsv1.JSON { 96 variablesMap := map[string]apiextensionsv1.JSON{} 97 for i := range variables { 98 variablesMap[variables[i].Name] = variables[i].Value 99 } 100 return variablesMap 101 } 102 103 // MergeVariableMaps merges variables. 104 // This func is useful when merging global and template-specific variables. 105 // NOTE: In case a variable exists in multiple maps, the variable from the latter map is preserved. 106 // NOTE: The builtin variable object is merged instead of simply overwritten. 107 func MergeVariableMaps(variableMaps ...map[string]apiextensionsv1.JSON) (map[string]apiextensionsv1.JSON, error) { 108 res := make(map[string]apiextensionsv1.JSON) 109 110 for _, variableMap := range variableMaps { 111 for variableName, variableValue := range variableMap { 112 // If the variable already exists and is the builtin variable, merge it. 113 if _, ok := res[variableName]; ok && variableName == runtimehooksv1.BuiltinsName { 114 mergedV, err := mergeBuiltinVariables(res[variableName], variableValue) 115 if err != nil { 116 return nil, errors.Wrapf(err, "failed to merge builtin variables") 117 } 118 res[variableName] = *mergedV 119 continue 120 } 121 res[variableName] = variableValue 122 } 123 } 124 125 return res, nil 126 } 127 128 // mergeBuiltinVariables merges builtin variable objects. 129 // NOTE: In case a variable exists in multiple builtin variables, the variable from the latter map is preserved. 130 func mergeBuiltinVariables(variableList ...apiextensionsv1.JSON) (*apiextensionsv1.JSON, error) { 131 builtins := &runtimehooksv1.Builtins{} 132 133 // Unmarshal all variables into builtins. 134 // NOTE: This accumulates the fields on the builtins. 135 // Fields will be overwritten by later Unmarshals if fields are 136 // set on multiple variables. 137 for _, variable := range variableList { 138 if err := json.Unmarshal(variable.Raw, builtins); err != nil { 139 return nil, errors.Wrapf(err, "failed to unmarshal builtin variable") 140 } 141 } 142 143 // Marshal builtins to JSON. 144 builtinVariableJSON, err := json.Marshal(builtins) 145 if err != nil { 146 return nil, errors.Wrapf(err, "failed to marshal builtin variable") 147 } 148 149 return &apiextensionsv1.JSON{ 150 Raw: builtinVariableJSON, 151 }, nil 152 }