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 }