sigs.k8s.io/cluster-api@v1.7.1/internal/topology/variables/cluster_variable_defaulting.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 variables
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
    25  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    26  	structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
    27  	structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
    28  	"k8s.io/apimachinery/pkg/util/validation/field"
    29  
    30  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    31  )
    32  
    33  // DefaultClusterVariables defaults ClusterVariables.
    34  func DefaultClusterVariables(values []clusterv1.ClusterVariable, definitions []clusterv1.ClusterClassStatusVariable, fldPath *field.Path) ([]clusterv1.ClusterVariable, field.ErrorList) {
    35  	return defaultClusterVariables(values, definitions, true, fldPath)
    36  }
    37  
    38  // DefaultMachineVariables defaults MachineDeploymentVariables and MachinePoolVariables.
    39  func DefaultMachineVariables(values []clusterv1.ClusterVariable, definitions []clusterv1.ClusterClassStatusVariable, fldPath *field.Path) ([]clusterv1.ClusterVariable, field.ErrorList) {
    40  	return defaultClusterVariables(values, definitions, false, fldPath)
    41  }
    42  
    43  // defaultClusterVariables defaults variables.
    44  // If they do not exist yet, they are created if createVariables is set.
    45  func defaultClusterVariables(values []clusterv1.ClusterVariable, definitions []clusterv1.ClusterClassStatusVariable, createVariables bool, fldPath *field.Path) ([]clusterv1.ClusterVariable, field.ErrorList) {
    46  	var allErrs field.ErrorList
    47  
    48  	// Get a map of ClusterVariable values. This function validates that:
    49  	// - variables are not defined more than once in Cluster spec.
    50  	// - variables with the same name do not have a mix of empty and non-empty DefinitionFrom.
    51  	valuesIndex, err := newValuesIndex(values)
    52  	if err != nil {
    53  		var valueStrings []string
    54  		for _, v := range values {
    55  			valueStrings = append(valueStrings, fmt.Sprintf("Name: %s DefinitionFrom: %s", v.Name, v.DefinitionFrom))
    56  		}
    57  		return nil, append(allErrs, field.Invalid(fldPath, "["+strings.Join(valueStrings, ",")+"]", fmt.Sprintf("cluster variables not valid: %s", err)))
    58  	}
    59  
    60  	// Get an index for each variable name and definition.
    61  	defIndex := newDefinitionsIndex(definitions)
    62  
    63  	// Get a deterministically ordered list of all variables defined in both the Cluster and the ClusterClass.
    64  	// Note: If the order is not deterministic variables would be continuously rewritten to the Cluster.
    65  	allVariables := getAllVariables(values, valuesIndex, definitions)
    66  
    67  	// Default all variables.
    68  	defaultedValues := []clusterv1.ClusterVariable{}
    69  	for _, variable := range allVariables {
    70  		// Get the variable definition from the ClusterClass. If the variable is not defined add an error.
    71  		definition, err := defIndex.get(variable.Name, variable.DefinitionFrom)
    72  		if err != nil {
    73  			allErrs = append(allErrs, field.Required(fldPath, err.Error()))
    74  			continue
    75  		}
    76  
    77  		// Get the current value of the variable if it is defined in the Cluster spec.
    78  		currentValue := getCurrentValue(variable, valuesIndex)
    79  
    80  		// Default the variable.
    81  		defaultedValue, errs := defaultValue(currentValue, definition, fldPath, createVariables)
    82  		if len(errs) > 0 {
    83  			allErrs = append(allErrs, errs...)
    84  			continue
    85  		}
    86  
    87  		// Continue if there is no defaulted variable.
    88  		// NOTE: This happens when the variable doesn't exist on the CLuster before and
    89  		// there is no top-level default value.
    90  		if defaultedValue == nil {
    91  			continue
    92  		}
    93  		defaultedValues = append(defaultedValues, *defaultedValue)
    94  	}
    95  
    96  	if len(allErrs) > 0 {
    97  		return nil, allErrs
    98  	}
    99  	return defaultedValues, nil
   100  }
   101  
   102  // getCurrentValue returns the value of a variable for its definitionFrom, or for an empty definitionFrom if it exists.
   103  func getCurrentValue(variable clusterv1.ClusterVariable, valuesMap map[string]map[string]clusterv1.ClusterVariable) *clusterv1.ClusterVariable {
   104  	// If the value is set in the Cluster spec get the value.
   105  	if valuesForName, ok := valuesMap[variable.Name]; ok {
   106  		if value, ok := valuesForName[variable.DefinitionFrom]; ok {
   107  			return &value
   108  		}
   109  	}
   110  	return nil
   111  }
   112  
   113  // defaultValue defaults a clusterVariable based on the default value in the clusterClassVariable.
   114  func defaultValue(currentValue *clusterv1.ClusterVariable, definition *statusVariableDefinition, fldPath *field.Path, createVariable bool) (*clusterv1.ClusterVariable, field.ErrorList) {
   115  	if currentValue == nil {
   116  		// Return if the variable does not exist yet and createVariable is false.
   117  		if !createVariable {
   118  			return nil, nil
   119  		}
   120  		// Return if the variable does not exist yet and there is no top-level default value.
   121  		if definition.Schema.OpenAPIV3Schema.Default == nil {
   122  			return nil, nil
   123  		}
   124  	}
   125  
   126  	// Convert schema to Kubernetes APIExtensions schema.
   127  	apiExtensionsSchema, errs := convertToAPIExtensionsJSONSchemaProps(&definition.Schema.OpenAPIV3Schema, field.NewPath("schema"))
   128  	if len(errs) > 0 {
   129  		return nil, field.ErrorList{field.Invalid(fldPath, "",
   130  			fmt.Sprintf("invalid schema in ClusterClass for variable %q: error to convert schema %v", definition.Name, errs))}
   131  	}
   132  
   133  	var value interface{}
   134  	// If the variable already exists, parse the current value.
   135  	if currentValue != nil && len(currentValue.Value.Raw) > 0 {
   136  		if err := json.Unmarshal(currentValue.Value.Raw, &value); err != nil {
   137  			return nil, field.ErrorList{field.Invalid(fldPath, "",
   138  				fmt.Sprintf("failed to unmarshal variable %q value %q: %v", currentValue.Name, string(currentValue.Value.Raw), err))}
   139  		}
   140  	}
   141  
   142  	// Structural schema defaulting does not work with scalar values,
   143  	// so we wrap the schema and the variable in objects.
   144  	// <variable-name>: <variable-value>
   145  	wrappedVariable := map[string]interface{}{
   146  		definition.Name: value,
   147  	}
   148  	// type: object
   149  	// properties:
   150  	//   <variable-name>: <variable-schema>
   151  	wrappedSchema := &apiextensions.JSONSchemaProps{
   152  		Type: "object",
   153  		Properties: map[string]apiextensions.JSONSchemaProps{
   154  			definition.Name: *apiExtensionsSchema,
   155  		},
   156  	}
   157  
   158  	// Default the variable via the structural schema library.
   159  	ss, err := structuralschema.NewStructural(wrappedSchema)
   160  	if err != nil {
   161  		return nil, field.ErrorList{field.Invalid(fldPath, "",
   162  			fmt.Sprintf("failed defaulting variable %q: %v", currentValue.Name, err))}
   163  	}
   164  	structuraldefaulting.Default(wrappedVariable, ss)
   165  
   166  	// Marshal the defaulted value.
   167  	defaultedVariableValue, err := json.Marshal(wrappedVariable[definition.Name])
   168  	if err != nil {
   169  		return nil, field.ErrorList{field.Invalid(fldPath, "",
   170  			fmt.Sprintf("failed to marshal default value of variable %q: %v", definition.Name, err))}
   171  	}
   172  	v := &clusterv1.ClusterVariable{
   173  		Name: definition.Name,
   174  		Value: apiextensionsv1.JSON{
   175  			Raw: defaultedVariableValue,
   176  		},
   177  		DefinitionFrom: definition.From,
   178  	}
   179  
   180  	return v, nil
   181  }
   182  
   183  // getAllVariables returns a correctly ordered list of all variables defined in the ClusterClass and the Cluster.
   184  func getAllVariables(values []clusterv1.ClusterVariable, valuesIndex map[string]map[string]clusterv1.ClusterVariable, definitions []clusterv1.ClusterClassStatusVariable) []clusterv1.ClusterVariable {
   185  	// allVariables is used to get a full correctly ordered list of variables.
   186  	allVariables := []clusterv1.ClusterVariable{}
   187  	uniqueVariableDefinitions := map[string]bool{}
   188  
   189  	// Add any values that already exist.
   190  	allVariables = append(allVariables, values...)
   191  
   192  	// Add variables from the ClusterClass, which currently don't exist on the Cluster.
   193  	for _, variable := range definitions {
   194  		for _, definition := range variable.Definitions {
   195  			definitionFrom := definition.From
   196  
   197  			// 1) If there is a value in the Cluster with this definitionFrom or with an empty definitionFrom this variable does not need to be defaulted.
   198  			if _, ok := valuesIndex[variable.Name]; ok {
   199  				if _, ok := valuesIndex[variable.Name][definitionFrom]; ok {
   200  					continue
   201  				}
   202  				if _, ok := valuesIndex[variable.Name][emptyDefinitionFrom]; ok {
   203  					continue
   204  				}
   205  			}
   206  
   207  			// 2) If the definition has no conflicts and no variable of the same name is defined in the Cluster set the definitionFrom to emptyDefinitionFrom.
   208  			if !variable.DefinitionsConflict && len(valuesIndex[variable.Name]) == 0 {
   209  				definitionFrom = emptyDefinitionFrom
   210  			}
   211  
   212  			// 3) If a variable with this name and definition has been added already, continue.
   213  			// This prevents adding the same variable multiple times where the variable is defaulted with an emptyDefinitionFrom.
   214  			if _, ok := uniqueVariableDefinitions[definitionFrom+variable.Name]; ok {
   215  				continue
   216  			}
   217  
   218  			// Otherwise add the variable to the list.
   219  			allVariables = append(allVariables, clusterv1.ClusterVariable{
   220  				Name:           variable.Name,
   221  				DefinitionFrom: definitionFrom,
   222  			})
   223  			uniqueVariableDefinitions[definitionFrom+variable.Name] = true
   224  		}
   225  	}
   226  	return allVariables
   227  }