sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/machineset/util.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 machineset
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"github.com/pkg/errors"
    24  	corev1 "k8s.io/api/core/v1"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	"k8s.io/apimachinery/pkg/runtime/schema"
    27  	"k8s.io/apimachinery/pkg/types"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  
    30  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    31  	"sigs.k8s.io/cluster-api/controllers/external"
    32  	tlog "sigs.k8s.io/cluster-api/internal/log"
    33  )
    34  
    35  // GetMachineSetsForDeployment returns a list of MachineSets associated with a MachineDeployment.
    36  func GetMachineSetsForDeployment(ctx context.Context, c client.Reader, md types.NamespacedName) ([]*clusterv1.MachineSet, error) {
    37  	// List MachineSets based on the MachineDeployment label.
    38  	msList := &clusterv1.MachineSetList{}
    39  	if err := c.List(ctx, msList,
    40  		client.InNamespace(md.Namespace), client.MatchingLabels{clusterv1.MachineDeploymentNameLabel: md.Name}); err != nil {
    41  		return nil, errors.Wrapf(err, "failed to list MachineSets for MachineDeployment/%s", md.Name)
    42  	}
    43  
    44  	// Copy the MachineSets to an array of MachineSet pointers, to avoid MachineSet copying later.
    45  	res := make([]*clusterv1.MachineSet, 0, len(msList.Items))
    46  	for i := range msList.Items {
    47  		res = append(res, &msList.Items[i])
    48  	}
    49  	return res, nil
    50  }
    51  
    52  // CalculateTemplatesInUse returns all templates referenced in non-deleting MachineDeployment and MachineSets.
    53  func CalculateTemplatesInUse(md *clusterv1.MachineDeployment, msList []*clusterv1.MachineSet) (map[string]bool, error) {
    54  	templatesInUse := map[string]bool{}
    55  
    56  	// Templates of the MachineSet are still in use if the MachineSet is not in deleting state.
    57  	for _, ms := range msList {
    58  		if !ms.DeletionTimestamp.IsZero() {
    59  			continue
    60  		}
    61  
    62  		bootstrapRef := ms.Spec.Template.Spec.Bootstrap.ConfigRef
    63  		infrastructureRef := &ms.Spec.Template.Spec.InfrastructureRef
    64  		if err := addTemplateRef(templatesInUse, bootstrapRef, infrastructureRef); err != nil {
    65  			return nil, errors.Wrapf(err, "failed to add templates of %s to templatesInUse", tlog.KObj{Obj: ms})
    66  		}
    67  	}
    68  
    69  	// If MachineDeployment has already been deleted or still exists and is in deleting state, then there
    70  	// are no templates referenced in the MachineDeployment which are still in use, so let's return here.
    71  	if md == nil || !md.DeletionTimestamp.IsZero() {
    72  		return templatesInUse, nil
    73  	}
    74  
    75  	//  Otherwise, the templates of the MachineDeployment are still in use.
    76  	bootstrapRef := md.Spec.Template.Spec.Bootstrap.ConfigRef
    77  	infrastructureRef := &md.Spec.Template.Spec.InfrastructureRef
    78  	if err := addTemplateRef(templatesInUse, bootstrapRef, infrastructureRef); err != nil {
    79  		return nil, errors.Wrapf(err, "failed to add templates of %s to templatesInUse", tlog.KObj{Obj: md})
    80  	}
    81  	return templatesInUse, nil
    82  }
    83  
    84  // DeleteTemplateIfUnused deletes the template (ref), if it is not in use (i.e. in templatesInUse).
    85  func DeleteTemplateIfUnused(ctx context.Context, c client.Client, templatesInUse map[string]bool, ref *corev1.ObjectReference) error {
    86  	// If ref is nil, do nothing (this can happen, because bootstrap templates are optional).
    87  	if ref == nil {
    88  		return nil
    89  	}
    90  
    91  	log := tlog.LoggerFrom(ctx).WithRef(ref)
    92  
    93  	refID, err := templateRefID(ref)
    94  	if err != nil {
    95  		return errors.Wrapf(err, "failed to calculate templateRefID")
    96  	}
    97  
    98  	// If the template is still in use, do nothing.
    99  	if templatesInUse[refID] {
   100  		log.V(3).Infof("Not deleting %s, because it's still in use", tlog.KRef{Ref: ref})
   101  		return nil
   102  	}
   103  
   104  	log.Infof("Deleting %s", tlog.KRef{Ref: ref})
   105  	if err := external.Delete(ctx, c, ref); err != nil && !apierrors.IsNotFound(err) {
   106  		return errors.Wrapf(err, "failed to delete %s", tlog.KRef{Ref: ref})
   107  	}
   108  	return nil
   109  }
   110  
   111  // addTemplateRef adds the refs to the refMap with the templateRefID as key.
   112  func addTemplateRef(refMap map[string]bool, refs ...*corev1.ObjectReference) error {
   113  	for _, ref := range refs {
   114  		if ref != nil {
   115  			refID, err := templateRefID(ref)
   116  			if err != nil {
   117  				return errors.Wrapf(err, "failed to calculate templateRefID")
   118  			}
   119  			refMap[refID] = true
   120  		}
   121  	}
   122  	return nil
   123  }
   124  
   125  // templateRefID returns the templateRefID of a ObjectReference in the format: g/k/name.
   126  // Note: We don't include the version as references with different versions should be treated as equal.
   127  func templateRefID(ref *corev1.ObjectReference) (string, error) {
   128  	if ref == nil {
   129  		return "", nil
   130  	}
   131  
   132  	gv, err := schema.ParseGroupVersion(ref.APIVersion)
   133  	if err != nil {
   134  		return "", errors.Wrapf(err, "failed to parse apiVersion %q", ref.APIVersion)
   135  	}
   136  
   137  	return fmt.Sprintf("%s/%s/%s", gv.Group, ref.Kind, ref.Name), nil
   138  }