github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/lib/operatorclient/deployment.go (about)

     1  package operatorclient
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	appsv1 "k8s.io/api/apps/v1"
    10  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  	"k8s.io/apimachinery/pkg/labels"
    13  	"k8s.io/apimachinery/pkg/types"
    14  	"k8s.io/apimachinery/pkg/util/wait"
    15  	"k8s.io/klog"
    16  )
    17  
    18  const (
    19  	deploymentRolloutPollInterval = time.Second
    20  )
    21  
    22  // GetDeployment returns the Deployment object for the given namespace and name.
    23  func (c *Client) GetDeployment(namespace, name string) (*appsv1.Deployment, error) {
    24  	klog.V(4).Infof("[GET Deployment]: %s:%s", namespace, name)
    25  	return c.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
    26  }
    27  
    28  // CreateDeployment creates the Deployment object or Updates if it already exists.
    29  func (c *Client) CreateDeployment(dep *appsv1.Deployment) (*appsv1.Deployment, error) {
    30  	klog.V(4).Infof("[CREATE Deployment]: %s:%s", dep.Namespace, dep.Name)
    31  	createdDep, err := c.AppsV1().Deployments(dep.Namespace).Create(context.TODO(), dep, metav1.CreateOptions{})
    32  	if apierrors.IsAlreadyExists(err) {
    33  		updatedDep, _, err := c.UpdateDeployment(dep)
    34  		return updatedDep, err
    35  	}
    36  	return createdDep, err
    37  }
    38  
    39  // DeleteDeployment deletes the Deployment object.
    40  func (c *Client) DeleteDeployment(namespace, name string, options *metav1.DeleteOptions) error {
    41  	klog.V(4).Infof("[DELETE Deployment]: %s:%s", namespace, name)
    42  	return c.AppsV1().Deployments(namespace).Delete(context.TODO(), name, *options)
    43  }
    44  
    45  // UpdateDeployment updates a Deployment object by performing a 2-way patch between the existing
    46  // Deployment and the result of the UpdateFunction.
    47  //
    48  // Returns the latest Deployment and true if it was updated, or an error.
    49  func (c *Client) UpdateDeployment(dep *appsv1.Deployment) (*appsv1.Deployment, bool, error) {
    50  	return c.PatchDeployment(nil, dep)
    51  }
    52  
    53  // PatchDeployment updates a Deployment object by performing a 3-way patch merge between the existing
    54  // Deployment and `original` and `modified` manifests.
    55  //
    56  // Returns the latest Deployment and true if it was updated, or an error.
    57  func (c *Client) PatchDeployment(original, modified *appsv1.Deployment) (*appsv1.Deployment, bool, error) {
    58  	namespace, name := modified.Namespace, modified.Name
    59  	klog.V(4).Infof("[PATCH Deployment]: %s:%s", namespace, name)
    60  
    61  	current, err := c.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
    62  	if err != nil {
    63  		return nil, false, err
    64  	}
    65  	if modified == nil {
    66  		return nil, false, errors.New("modified cannot be nil")
    67  	}
    68  	if original == nil {
    69  		original = current // Emulate 2-way merge.
    70  	}
    71  	current.TypeMeta = modified.TypeMeta // make sure the type metas won't conflict.
    72  	patchBytes, err := createThreeWayMergePatchPreservingCommands(original, modified, current)
    73  	if err != nil {
    74  		return nil, false, err
    75  	}
    76  	updated, err := c.AppsV1().Deployments(namespace).Patch(context.TODO(), name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{})
    77  	if err != nil {
    78  		return nil, false, err
    79  	}
    80  	return updated, current.GetResourceVersion() != updated.GetResourceVersion(), nil
    81  }
    82  
    83  // RollingUpdateDeployment performs a rolling update on the given Deployment. It requires that the
    84  // Deployment uses the RollingUpdateDeploymentStrategyType update strategy.
    85  func (c *Client) RollingUpdateDeployment(dep *appsv1.Deployment) (*appsv1.Deployment, bool, error) {
    86  	return c.RollingUpdateDeploymentMigrations(dep.Namespace, dep.Name, Update(dep))
    87  }
    88  
    89  // RollingUpdateDeploymentMigrations performs a rolling update on the given Deployment. It
    90  // requires that the Deployment uses the RollingUpdateDeploymentStrategyType update strategy.
    91  //
    92  // RollingUpdateDeploymentMigrations will run any before / during / after migrations that have been
    93  // specified in the upgrade options.
    94  func (c *Client) RollingUpdateDeploymentMigrations(namespace, name string, f UpdateFunction) (*appsv1.Deployment, bool, error) {
    95  	klog.V(4).Infof("[ROLLING UPDATE Deployment]: %s:%s", namespace, name)
    96  	return c.RollingPatchDeploymentMigrations(namespace, name, updateToPatch(f))
    97  }
    98  
    99  // RollingPatchDeployment performs a 3-way patch merge followed by rolling update on the given
   100  // Deployment. It requires that the Deployment uses the RollingUpdateDeploymentStrategyType update
   101  // strategy.
   102  //
   103  // RollingPatchDeployment will run any before / after migrations that have been specified in the
   104  // upgrade options.
   105  func (c *Client) RollingPatchDeployment(original, modified *appsv1.Deployment) (*appsv1.Deployment, bool, error) {
   106  	return c.RollingPatchDeploymentMigrations(modified.Namespace, modified.Name, Patch(original, modified))
   107  }
   108  
   109  // RollingPatchDeploymentMigrations performs a 3-way patch merge followed by rolling update on
   110  // the given Deployment. It requires that the Deployment uses the RollingUpdateDeploymentStrategyType
   111  // update strategy.
   112  //
   113  // RollingPatchDeploymentMigrations will run any before / after migrations that have been specified
   114  // in the upgrade options.
   115  func (c *Client) RollingPatchDeploymentMigrations(namespace, name string, f PatchFunction) (*appsv1.Deployment, bool, error) {
   116  	klog.V(4).Infof("[ROLLING PATCH Deployment]: %s:%s", namespace, name)
   117  
   118  	current, err := c.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
   119  	if err != nil {
   120  		return nil, false, err
   121  	}
   122  	if err := checkDeploymentRollingUpdateEnabled(current); err != nil {
   123  		return nil, false, err
   124  	}
   125  
   126  	originalObj, modifiedObj, err := f(current.DeepCopy())
   127  	if err != nil {
   128  		return nil, false, err
   129  	}
   130  	// Check for nil interfaces.
   131  	if modifiedObj == nil {
   132  		return nil, false, errors.New("modified cannot be nil")
   133  	}
   134  	if originalObj == nil {
   135  		originalObj = current // Emulate 2-way merge.
   136  	}
   137  	original, modified := originalObj.(*appsv1.Deployment), modifiedObj.(*appsv1.Deployment)
   138  	// Check for nil pointers.
   139  	if modified == nil {
   140  		return nil, false, errors.New("modified cannot be nil")
   141  	}
   142  	if original == nil {
   143  		original = current // Emulate 2-way merge.
   144  	}
   145  	current.TypeMeta = modified.TypeMeta // make sure the type metas won't conflict.
   146  	patchBytes, err := createThreeWayMergePatchPreservingCommands(original, modified, current)
   147  	if err != nil {
   148  		return nil, false, err
   149  	}
   150  	updated, err := c.AppsV1().Deployments(namespace).Patch(context.TODO(), name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{})
   151  	if err != nil {
   152  		return nil, false, err
   153  	}
   154  
   155  	return updated, current.GetResourceVersion() != updated.GetResourceVersion(), nil
   156  }
   157  
   158  func checkDeploymentRollingUpdateEnabled(dep *appsv1.Deployment) error {
   159  	enabled := dep.Spec.Strategy.Type == appsv1.RollingUpdateDeploymentStrategyType || dep.Spec.Strategy.Type == "" // Deployments rolling update by default
   160  	if !enabled {
   161  		return fmt.Errorf("Deployment %s/%s does not have rolling update strategy enabled", dep.GetNamespace(), dep.GetName())
   162  	}
   163  	return nil
   164  }
   165  
   166  func (c *Client) waitForDeploymentRollout(dep *appsv1.Deployment) error {
   167  	return wait.PollInfinite(deploymentRolloutPollInterval, func() (bool, error) {
   168  		d, err := c.GetDeployment(dep.Namespace, dep.Name)
   169  		if err != nil {
   170  			// Do not return error here, as we could be updating the API Server itself, in which case we
   171  			// want to continue waiting.
   172  			klog.Errorf("error getting Deployment %s during rollout: %v", dep.Name, err)
   173  			return false, nil
   174  		}
   175  		if d.Generation <= d.Status.ObservedGeneration && d.Status.UpdatedReplicas == d.Status.Replicas && d.Status.UnavailableReplicas == 0 {
   176  			return true, nil
   177  		}
   178  		return false, nil
   179  	})
   180  }
   181  
   182  // CreateOrRollingUpdateDeployment creates the Deployment if it doesn't exist. If the Deployment
   183  // already exists, it will update the Deployment and wait for it to rollout. Returns true if the
   184  // Deployment was created or updated, false if there was no update.
   185  func (c *Client) CreateOrRollingUpdateDeployment(dep *appsv1.Deployment) (*appsv1.Deployment, bool, error) {
   186  	klog.V(4).Infof("[CREATE OR ROLLING UPDATE Deployment]: %s:%s", dep.Namespace, dep.Name)
   187  
   188  	_, err := c.GetDeployment(dep.Namespace, dep.Name)
   189  	if err != nil {
   190  		if !apierrors.IsNotFound(err) {
   191  			return nil, false, err
   192  		}
   193  		created, err := c.CreateDeployment(dep)
   194  		if err != nil {
   195  			return nil, false, err
   196  		}
   197  		return created, true, err
   198  	}
   199  	return c.RollingUpdateDeployment(dep)
   200  }
   201  
   202  // ListDeploymentsWithLabels returns a list of deployments that matches the label selector.
   203  // An empty list will be returned if no such deployments is found.
   204  func (c *Client) ListDeploymentsWithLabels(namespace string, labels labels.Set) (*appsv1.DeploymentList, error) {
   205  	klog.V(4).Infof("[LIST Deployments] in %s, labels: %v", namespace, labels)
   206  
   207  	opts := metav1.ListOptions{LabelSelector: labels.String()}
   208  	return c.AppsV1().Deployments(namespace).List(context.TODO(), opts)
   209  }