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  }