sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/alpha/machinedeployment.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 alpha
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strconv"
    23  	"time"
    24  
    25  	"github.com/pkg/errors"
    26  	"k8s.io/apimachinery/pkg/api/meta"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/labels"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  
    33  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    34  	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
    35  	logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
    36  )
    37  
    38  // getMachineDeployment retrieves the MachineDeployment object corresponding to the name and namespace specified.
    39  func getMachineDeployment(ctx context.Context, proxy cluster.Proxy, name, namespace string) (*clusterv1.MachineDeployment, error) {
    40  	mdObj := &clusterv1.MachineDeployment{}
    41  	c, err := proxy.NewClient(ctx)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	mdObjKey := client.ObjectKey{
    46  		Namespace: namespace,
    47  		Name:      name,
    48  	}
    49  	if err := c.Get(ctx, mdObjKey, mdObj); err != nil {
    50  		return nil, errors.Wrapf(err, "failed to get MachineDeployment %s/%s",
    51  			mdObjKey.Namespace, mdObjKey.Name)
    52  	}
    53  	return mdObj, nil
    54  }
    55  
    56  // setRolloutAfterOnMachineDeployment sets MachineDeployment.spec.rolloutAfter.
    57  func setRolloutAfterOnMachineDeployment(ctx context.Context, proxy cluster.Proxy, name, namespace string) error {
    58  	patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf(`{"spec":{"rolloutAfter":"%v"}}`, time.Now().Format(time.RFC3339))))
    59  	return patchMachineDeployment(ctx, proxy, name, namespace, patch)
    60  }
    61  
    62  // patchMachineDeployment applies a patch to a machinedeployment.
    63  func patchMachineDeployment(ctx context.Context, proxy cluster.Proxy, name, namespace string, patch client.Patch) error {
    64  	cFrom, err := proxy.NewClient(ctx)
    65  	if err != nil {
    66  		return err
    67  	}
    68  	mdObj := &clusterv1.MachineDeployment{}
    69  	mdObjKey := client.ObjectKey{
    70  		Namespace: namespace,
    71  		Name:      name,
    72  	}
    73  	if err := cFrom.Get(ctx, mdObjKey, mdObj); err != nil {
    74  		return errors.Wrapf(err, "failed to get MachineDeployment %s/%s", mdObj.GetNamespace(), mdObj.GetName())
    75  	}
    76  
    77  	if err := cFrom.Patch(ctx, mdObj, patch); err != nil {
    78  		return errors.Wrapf(err, "failed while patching MachineDeployment %s/%s", mdObj.GetNamespace(), mdObj.GetName())
    79  	}
    80  	return nil
    81  }
    82  
    83  // findMachineDeploymentRevision finds the specific revision in the machine sets.
    84  func findMachineDeploymentRevision(toRevision int64, allMSs []*clusterv1.MachineSet) (*clusterv1.MachineSet, error) {
    85  	var (
    86  		latestMachineSet   *clusterv1.MachineSet
    87  		latestRevision     = int64(-1)
    88  		previousMachineSet *clusterv1.MachineSet
    89  		previousRevision   = int64(-1)
    90  	)
    91  	for _, ms := range allMSs {
    92  		if v, err := revision(ms); err == nil {
    93  			if toRevision == 0 {
    94  				if latestRevision < v {
    95  					// newest one we've seen so far
    96  					previousRevision = latestRevision
    97  					previousMachineSet = latestMachineSet
    98  					latestRevision = v
    99  					latestMachineSet = ms
   100  				} else if previousRevision < v {
   101  					// second newest one we've seen so far
   102  					previousRevision = v
   103  					previousMachineSet = ms
   104  				}
   105  			} else if toRevision == v {
   106  				return ms, nil
   107  			}
   108  		}
   109  	}
   110  
   111  	if toRevision > 0 {
   112  		return nil, errors.Errorf("unable to find specified MachineDeployment revision: %v", toRevision)
   113  	}
   114  
   115  	if previousMachineSet == nil {
   116  		return nil, errors.Errorf("no rollout history found for MachineDeployment")
   117  	}
   118  	return previousMachineSet, nil
   119  }
   120  
   121  // getMachineSetsForDeployment returns a list of MachineSets associated with a MachineDeployment.
   122  func getMachineSetsForDeployment(ctx context.Context, proxy cluster.Proxy, md *clusterv1.MachineDeployment) ([]*clusterv1.MachineSet, error) {
   123  	log := logf.Log
   124  	c, err := proxy.NewClient(ctx)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	// List all MachineSets to find those we own but that no longer match our selector.
   129  	machineSets := &clusterv1.MachineSetList{}
   130  	if err := c.List(ctx, machineSets, client.InNamespace(md.Namespace)); err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	filtered := make([]*clusterv1.MachineSet, 0, len(machineSets.Items))
   135  	for idx := range machineSets.Items {
   136  		ms := &machineSets.Items[idx]
   137  
   138  		// Skip this MachineSet if its controller ref is not pointing to this MachineDeployment
   139  		if !metav1.IsControlledBy(ms, md) {
   140  			log.V(5).Info("Skipping MachineSet, controller ref does not match MachineDeployment", "machineset", ms.Name)
   141  			continue
   142  		}
   143  
   144  		selector, err := metav1.LabelSelectorAsSelector(&md.Spec.Selector)
   145  		if err != nil {
   146  			log.V(5).Info("Skipping MachineSet, failed to get label selector from spec selector", "machineset", ms.Name)
   147  			continue
   148  		}
   149  		// If a MachineDeployment with a nil or empty selector creeps in, it should match nothing, not everything.
   150  		if selector.Empty() {
   151  			log.V(5).Info("Skipping MachineSet as the selector is empty", "machineset", ms.Name)
   152  			continue
   153  		}
   154  		// Skip this MachineSet if selector does not match
   155  		if !selector.Matches(labels.Set(ms.Labels)) {
   156  			log.V(5).Info("Skipping MachineSet, label mismatch", "machineset", ms.Name)
   157  			continue
   158  		}
   159  		filtered = append(filtered, ms)
   160  	}
   161  
   162  	return filtered, nil
   163  }
   164  
   165  func revision(obj runtime.Object) (int64, error) {
   166  	acc, err := meta.Accessor(obj)
   167  	if err != nil {
   168  		return 0, err
   169  	}
   170  	v, ok := acc.GetAnnotations()[clusterv1.RevisionAnnotation]
   171  	if !ok {
   172  		return 0, nil
   173  	}
   174  	return strconv.ParseInt(v, 10, 64)
   175  }