sigs.k8s.io/cluster-api@v1.7.1/exp/addons/internal/controllers/clusterresourceset_helpers.go (about)

     1  /*
     2  Copyright 2020 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 controllers
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"context"
    23  	"crypto/sha256"
    24  	"encoding/base64"
    25  	"encoding/json"
    26  	"fmt"
    27  	"sort"
    28  	"unicode"
    29  
    30  	"github.com/pkg/errors"
    31  	corev1 "k8s.io/api/core/v1"
    32  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    35  	"k8s.io/apimachinery/pkg/runtime/schema"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	"k8s.io/klog/v2"
    38  	"sigs.k8s.io/controller-runtime/pkg/client"
    39  
    40  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    41  	addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1"
    42  	utilresource "sigs.k8s.io/cluster-api/util/resource"
    43  	utilyaml "sigs.k8s.io/cluster-api/util/yaml"
    44  )
    45  
    46  var jsonListPrefix = []byte("[")
    47  
    48  // objsFromYamlData parses a collection of yaml documents into Unstructured objects.
    49  // The returned objects are sorted for creation priority within the objects defined
    50  // in the same document. The flattening of the documents preserves the original order.
    51  func objsFromYamlData(yamlDocs [][]byte) ([]unstructured.Unstructured, error) {
    52  	allObjs := []unstructured.Unstructured{}
    53  	for _, data := range yamlDocs {
    54  		isJSONList, err := isJSONList(data)
    55  		if err != nil {
    56  			return nil, err
    57  		}
    58  		objs := []unstructured.Unstructured{}
    59  		// If it is a json list, convert each list element to an unstructured object.
    60  		if isJSONList {
    61  			var results []map[string]interface{}
    62  			// Unmarshal the JSON to the interface.
    63  			if err = json.Unmarshal(data, &results); err == nil {
    64  				for i := range results {
    65  					var u unstructured.Unstructured
    66  					u.SetUnstructuredContent(results[i])
    67  					objs = append(objs, u)
    68  				}
    69  			}
    70  		} else {
    71  			// If it is not a json list, data is either json or yaml format.
    72  			objs, err = utilyaml.ToUnstructured(data)
    73  			if err != nil {
    74  				return nil, errors.Wrapf(err, "failed converting data to unstructured objects")
    75  			}
    76  		}
    77  
    78  		allObjs = append(allObjs, utilresource.SortForCreate(objs)...)
    79  	}
    80  
    81  	return allObjs, nil
    82  }
    83  
    84  // isJSONList returns whether the data is in JSON list format.
    85  func isJSONList(data []byte) (bool, error) {
    86  	const peekSize = 32
    87  	buffer := bufio.NewReaderSize(bytes.NewReader(data), peekSize)
    88  	b, err := buffer.Peek(peekSize)
    89  	if err != nil {
    90  		return false, err
    91  	}
    92  	trim := bytes.TrimLeftFunc(b, unicode.IsSpace)
    93  	return bytes.HasPrefix(trim, jsonListPrefix), nil
    94  }
    95  
    96  func createUnstructured(ctx context.Context, c client.Client, obj *unstructured.Unstructured) error {
    97  	if err := c.Create(ctx, obj); err != nil {
    98  		return errors.Wrapf(
    99  			err,
   100  			"creating object %s %s",
   101  			obj.GroupVersionKind(),
   102  			klog.KObj(obj),
   103  		)
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  // getOrCreateClusterResourceSetBinding retrieves ClusterResourceSetBinding resource owned by the cluster or create a new one if not found.
   110  func (r *ClusterResourceSetReconciler) getOrCreateClusterResourceSetBinding(ctx context.Context, cluster *clusterv1.Cluster, clusterResourceSet *addonsv1.ClusterResourceSet) (*addonsv1.ClusterResourceSetBinding, error) {
   111  	clusterResourceSetBinding := &addonsv1.ClusterResourceSetBinding{}
   112  	clusterResourceSetBindingKey := client.ObjectKey{
   113  		Namespace: cluster.Namespace,
   114  		Name:      cluster.Name,
   115  	}
   116  
   117  	if err := r.Client.Get(ctx, clusterResourceSetBindingKey, clusterResourceSetBinding); err != nil {
   118  		if !apierrors.IsNotFound(err) {
   119  			return nil, err
   120  		}
   121  		clusterResourceSetBinding.Name = cluster.Name
   122  		clusterResourceSetBinding.Namespace = cluster.Namespace
   123  		clusterResourceSetBinding.OwnerReferences = []metav1.OwnerReference{
   124  			{
   125  				APIVersion: addonsv1.GroupVersion.String(),
   126  				Kind:       "ClusterResourceSet",
   127  				Name:       clusterResourceSet.Name,
   128  				UID:        clusterResourceSet.UID,
   129  			},
   130  		}
   131  		clusterResourceSetBinding.Spec.Bindings = []*addonsv1.ResourceSetBinding{}
   132  		clusterResourceSetBinding.Spec.ClusterName = cluster.Name
   133  		if err := r.Client.Create(ctx, clusterResourceSetBinding); err != nil {
   134  			if apierrors.IsAlreadyExists(err) {
   135  				if err = r.Client.Get(ctx, clusterResourceSetBindingKey, clusterResourceSetBinding); err != nil {
   136  					return nil, err
   137  				}
   138  				return clusterResourceSetBinding, nil
   139  			}
   140  			return nil, errors.Wrapf(err, "failed to create clusterResourceSetBinding for cluster: %s/%s", cluster.Namespace, cluster.Name)
   141  		}
   142  	}
   143  	return clusterResourceSetBinding, nil
   144  }
   145  
   146  // getConfigMap retrieves any ConfigMap from the given name and namespace.
   147  func getConfigMap(ctx context.Context, c client.Client, configmapName types.NamespacedName) (*corev1.ConfigMap, error) {
   148  	configMap := &corev1.ConfigMap{}
   149  	configMapKey := client.ObjectKey{
   150  		Namespace: configmapName.Namespace,
   151  		Name:      configmapName.Name,
   152  	}
   153  	if err := c.Get(ctx, configMapKey, configMap); err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	return configMap, nil
   158  }
   159  
   160  // getSecret retrieves any Secret from the given secret name and namespace.
   161  func getSecret(ctx context.Context, c client.Client, secretName types.NamespacedName) (*corev1.Secret, error) {
   162  	secret := &corev1.Secret{}
   163  	secretKey := client.ObjectKey{
   164  		Namespace: secretName.Namespace,
   165  		Name:      secretName.Name,
   166  	}
   167  	if err := c.Get(ctx, secretKey, secret); err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	return secret, nil
   172  }
   173  
   174  func computeHash(dataArr [][]byte) string {
   175  	hash := sha256.New()
   176  	for i := range dataArr {
   177  		_, err := hash.Write(dataArr[i])
   178  		if err != nil {
   179  			continue
   180  		}
   181  	}
   182  	return fmt.Sprintf("sha256:%x", hash.Sum(nil))
   183  }
   184  
   185  // normalizeData reads content of the resource (configmap or secret) and returns
   186  // them serialized with constant order. Secret's data is base64 decoded.
   187  // This is useful to achieve consistent data  between runs, since the content
   188  // of the data field is a map and its order is non-deterministic.
   189  func normalizeData(resource *unstructured.Unstructured) ([][]byte, error) {
   190  	// Since maps are not ordered, we need to order them to get the same hash at each reconcile.
   191  	keys := make([]string, 0)
   192  	data, ok := resource.UnstructuredContent()["data"]
   193  	if !ok {
   194  		return nil, errors.Errorf("failed to get data field from resource %s", klog.KObj(resource))
   195  	}
   196  
   197  	unstructuredData := data.(map[string]interface{})
   198  	for key := range unstructuredData {
   199  		keys = append(keys, key)
   200  	}
   201  	sort.Strings(keys)
   202  
   203  	dataList := make([][]byte, 0)
   204  	for _, key := range keys {
   205  		val, ok, err := unstructured.NestedString(unstructuredData, key)
   206  		if err != nil {
   207  			return nil, errors.Wrapf(err, "getting value for field %s in data from resource %s", key, klog.KObj(resource))
   208  		}
   209  		if !ok {
   210  			return nil, errors.Errorf("value for field %s not present in data from resource %s", key, klog.KObj(resource))
   211  		}
   212  
   213  		byteArr := []byte(val)
   214  		// If the resource is a Secret, data needs to be decoded.
   215  		if resource.GetKind() == string(addonsv1.SecretClusterResourceSetResourceKind) {
   216  			byteArr, _ = base64.StdEncoding.DecodeString(val)
   217  		}
   218  
   219  		dataList = append(dataList, byteArr)
   220  	}
   221  
   222  	return dataList, nil
   223  }
   224  
   225  func getClusterNameFromOwnerRef(obj metav1.ObjectMeta) (string, error) {
   226  	for _, ref := range obj.GetOwnerReferences() {
   227  		if ref.Kind != "Cluster" {
   228  			continue
   229  		}
   230  		gv, err := schema.ParseGroupVersion(ref.APIVersion)
   231  		if err != nil {
   232  			return "", errors.Wrap(err, "failed to find cluster name in ownerRefs")
   233  		}
   234  
   235  		if gv.Group != clusterv1.GroupVersion.Group {
   236  			continue
   237  		}
   238  		if ref.Name == "" {
   239  			return "", errors.New("failed to find cluster name in ownerRefs: ref name is empty")
   240  		}
   241  		return ref.Name, nil
   242  	}
   243  	return "", errors.New("failed to find cluster name in ownerRefs: no cluster ownerRef")
   244  }
   245  
   246  // ensureKubernetesServiceCreated ensures that the Service for Kubernetes API Server has been created.
   247  func ensureKubernetesServiceCreated(ctx context.Context, client client.Client) error {
   248  	err := client.Get(ctx, types.NamespacedName{
   249  		Namespace: metav1.NamespaceDefault,
   250  		Name:      "kubernetes",
   251  	}, &corev1.Service{})
   252  	if err != nil {
   253  		return err
   254  	}
   255  	return nil
   256  }