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  }