github.com/oam-dev/kubevela@v1.9.11/pkg/rollout/rollout.go (about) 1 /* 2 Copyright 2021 The KubeVela 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 rollout 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 24 "github.com/pkg/errors" 25 k8stypes "k8s.io/apimachinery/pkg/types" 26 "k8s.io/client-go/util/retry" 27 "sigs.k8s.io/controller-runtime/pkg/client" 28 29 "github.com/oam-dev/kubevela/pkg/oam" 30 31 kruisev1alpha1 "github.com/openkruise/rollouts/api/v1alpha1" 32 33 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 34 "github.com/oam-dev/kubevela/pkg/multicluster" 35 "github.com/oam-dev/kubevela/pkg/resourcetracker" 36 velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors" 37 ) 38 39 // ClusterRollout rollout in specified cluster 40 type ClusterRollout struct { 41 *kruisev1alpha1.Rollout 42 Cluster string 43 } 44 45 func getAssociatedRollouts(ctx context.Context, cli client.Client, app *v1beta1.Application, withHistoryRTs bool) ([]*ClusterRollout, error) { 46 rootRT, currentRT, historyRTs, _, err := resourcetracker.ListApplicationResourceTrackers(ctx, cli, app) 47 if err != nil { 48 return nil, errors.Wrapf(err, "failed to list resource trackers") 49 } 50 if !withHistoryRTs { 51 historyRTs = []*v1beta1.ResourceTracker{} 52 } 53 var rollouts []*ClusterRollout 54 for _, rt := range append(historyRTs, rootRT, currentRT) { 55 if rt == nil { 56 continue 57 } 58 for _, mr := range rt.Spec.ManagedResources { 59 if mr.APIVersion == kruisev1alpha1.SchemeGroupVersion.String() && mr.Kind == "Rollout" { 60 rollout := &kruisev1alpha1.Rollout{} 61 if err = cli.Get(multicluster.ContextWithClusterName(ctx, mr.Cluster), k8stypes.NamespacedName{Namespace: mr.Namespace, Name: mr.Name}, rollout); err != nil { 62 if multicluster.IsNotFoundOrClusterNotExists(err) || velaerrors.IsCRDNotExists(err) { 63 continue 64 } 65 return nil, errors.Wrapf(err, "failed to get kruise rollout %s/%s in cluster %s", mr.Namespace, mr.Name, mr.Cluster) 66 } 67 if value, ok := rollout.Annotations[oam.AnnotationSkipResume]; ok && value == "true" { 68 continue 69 } 70 rollouts = append(rollouts, &ClusterRollout{Rollout: rollout, Cluster: mr.Cluster}) 71 } 72 } 73 } 74 return rollouts, nil 75 } 76 77 // SuspendRollout find all rollouts associated with the application (including history RTs) and resume them 78 func SuspendRollout(ctx context.Context, cli client.Client, app *v1beta1.Application, writer io.Writer) error { 79 rollouts, err := getAssociatedRollouts(ctx, cli, app, true) 80 if err != nil { 81 return err 82 } 83 for i := range rollouts { 84 rollout := rollouts[i] 85 if rollout.Status.Phase == kruisev1alpha1.RolloutPhaseProgressing && !rollout.Spec.Strategy.Paused { 86 _ctx := multicluster.ContextWithClusterName(ctx, rollout.Cluster) 87 rolloutKey := client.ObjectKeyFromObject(rollout.Rollout) 88 if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error { 89 if err = cli.Get(_ctx, rolloutKey, rollout.Rollout); err != nil { 90 return err 91 } 92 if rollout.Status.Phase == kruisev1alpha1.RolloutPhaseProgressing && !rollout.Spec.Strategy.Paused { 93 rollout.Spec.Strategy.Paused = true 94 if err = cli.Update(_ctx, rollout.Rollout); err != nil { 95 return err 96 } 97 if writer != nil { 98 _, _ = fmt.Fprintf(writer, "Rollout %s/%s in cluster %s suspended.\n", rollout.Namespace, rollout.Name, rollout.Cluster) 99 } 100 return nil 101 } 102 return nil 103 }); err != nil { 104 return errors.Wrapf(err, "failed to suspend rollout %s/%s in cluster %s", rollout.Namespace, rollout.Name, rollout.Cluster) 105 } 106 } 107 } 108 return nil 109 } 110 111 // ResumeRollout find all rollouts associated with the application (in the current RT) and resume them 112 func ResumeRollout(ctx context.Context, cli client.Client, app *v1beta1.Application, writer io.Writer) (bool, error) { 113 rollouts, err := getAssociatedRollouts(ctx, cli, app, false) 114 if err != nil { 115 return false, err 116 } 117 modified := false 118 for i := range rollouts { 119 rollout := rollouts[i] 120 if rollout.Spec.Strategy.Paused || (rollout.Status.CanaryStatus != nil && rollout.Status.CanaryStatus.CurrentStepState == kruisev1alpha1.CanaryStepStatePaused) { 121 _ctx := multicluster.ContextWithClusterName(ctx, rollout.Cluster) 122 rolloutKey := client.ObjectKeyFromObject(rollout.Rollout) 123 resumed := false 124 if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error { 125 if err = cli.Get(_ctx, rolloutKey, rollout.Rollout); err != nil { 126 return err 127 } 128 if rollout.Spec.Strategy.Paused { 129 rollout.Spec.Strategy.Paused = false 130 if err = cli.Update(_ctx, rollout.Rollout); err != nil { 131 return err 132 } 133 resumed = true 134 return nil 135 } 136 return nil 137 }); err != nil { 138 return false, errors.Wrapf(err, "failed to resume rollout %s/%s in cluster %s", rollout.Namespace, rollout.Name, rollout.Cluster) 139 } 140 if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error { 141 if err = cli.Get(_ctx, rolloutKey, rollout.Rollout); err != nil { 142 return err 143 } 144 if rollout.Status.CanaryStatus != nil && rollout.Status.CanaryStatus.CurrentStepState == kruisev1alpha1.CanaryStepStatePaused { 145 rollout.Status.CanaryStatus.CurrentStepState = kruisev1alpha1.CanaryStepStateReady 146 if err = cli.Status().Update(_ctx, rollout.Rollout); err != nil { 147 return err 148 } 149 resumed = true 150 return nil 151 } 152 return nil 153 }); err != nil { 154 return false, errors.Wrapf(err, "failed to resume rollout %s/%s in cluster %s", rollout.Namespace, rollout.Name, rollout.Cluster) 155 } 156 if resumed { 157 modified = true 158 if writer != nil { 159 _, _ = fmt.Fprintf(writer, "Rollout %s/%s in cluster %s resumed.\n", rollout.Namespace, rollout.Name, rollout.Cluster) 160 } 161 } 162 } 163 } 164 return modified, nil 165 } 166 167 // RollbackRollout find all rollouts associated with the application (in the current RT) and disable the pause field. 168 func RollbackRollout(ctx context.Context, cli client.Client, app *v1beta1.Application, writer io.Writer) (bool, error) { 169 rollouts, err := getAssociatedRollouts(ctx, cli, app, false) 170 if err != nil { 171 return false, err 172 } 173 modified := false 174 for i := range rollouts { 175 rollout := rollouts[i] 176 if rollout.Spec.Strategy.Paused || (rollout.Status.CanaryStatus != nil && rollout.Status.CanaryStatus.CurrentStepState == kruisev1alpha1.CanaryStepStatePaused) { 177 _ctx := multicluster.ContextWithClusterName(ctx, rollout.Cluster) 178 rolloutKey := client.ObjectKeyFromObject(rollout.Rollout) 179 resumed := false 180 if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error { 181 if err = cli.Get(_ctx, rolloutKey, rollout.Rollout); err != nil { 182 return err 183 } 184 if rollout.Spec.Strategy.Paused { 185 rollout.Spec.Strategy.Paused = false 186 if err = cli.Update(_ctx, rollout.Rollout); err != nil { 187 return err 188 } 189 resumed = true 190 return nil 191 } 192 return nil 193 }); err != nil { 194 return false, errors.Wrapf(err, "failed to rollback rollout %s/%s in cluster %s", rollout.Namespace, rollout.Name, rollout.Cluster) 195 } 196 if resumed { 197 modified = true 198 if writer != nil { 199 _, _ = fmt.Fprintf(writer, "Rollout %s/%s in cluster %s rollback.\n", rollout.Namespace, rollout.Name, rollout.Cluster) 200 } 201 } 202 } 203 } 204 return modified, nil 205 }