github.com/azure-devops-engineer/helm@v3.0.0-alpha.2+incompatible/pkg/action/rollback.go (about) 1 /* 2 Copyright The Helm 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 action 18 19 import ( 20 "bytes" 21 "fmt" 22 "sort" 23 "time" 24 25 "github.com/pkg/errors" 26 27 "helm.sh/helm/pkg/hooks" 28 "helm.sh/helm/pkg/release" 29 ) 30 31 // Rollback is the action for rolling back to a given release. 32 // 33 // It provides the implementation of 'helm rollback'. 34 type Rollback struct { 35 cfg *Configuration 36 37 Version int 38 Timeout time.Duration 39 Wait bool 40 DisableHooks bool 41 DryRun bool 42 Recreate bool // will (if true) recreate pods after a rollback. 43 Force bool // will (if true) force resource upgrade through uninstall/recreate if needed 44 } 45 46 // NewRollback creates a new Rollback object with the given configuration. 47 func NewRollback(cfg *Configuration) *Rollback { 48 return &Rollback{ 49 cfg: cfg, 50 } 51 } 52 53 // Run executes 'helm rollback' against the given release. 54 func (r *Rollback) Run(name string) (*release.Release, error) { 55 r.cfg.Log("preparing rollback of %s", name) 56 currentRelease, targetRelease, err := r.prepareRollback(name) 57 if err != nil { 58 return nil, err 59 } 60 61 if !r.DryRun { 62 r.cfg.Log("creating rolled back release for %s", name) 63 if err := r.cfg.Releases.Create(targetRelease); err != nil { 64 return nil, err 65 } 66 } 67 r.cfg.Log("performing rollback of %s", name) 68 res, err := r.performRollback(currentRelease, targetRelease) 69 if err != nil { 70 return res, err 71 } 72 73 if !r.DryRun { 74 r.cfg.Log("updating status for rolled back release for %s", name) 75 if err := r.cfg.Releases.Update(targetRelease); err != nil { 76 return res, err 77 } 78 } 79 80 return res, nil 81 } 82 83 // prepareRollback finds the previous release and prepares a new release object with 84 // the previous release's configuration 85 func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, error) { 86 if err := validateReleaseName(name); err != nil { 87 return nil, nil, errors.Errorf("prepareRollback: Release name is invalid: %s", name) 88 } 89 90 if r.Version < 0 { 91 return nil, nil, errInvalidRevision 92 } 93 94 currentRelease, err := r.cfg.Releases.Last(name) 95 if err != nil { 96 return nil, nil, err 97 } 98 99 previousVersion := r.Version 100 if r.Version == 0 { 101 previousVersion = currentRelease.Version - 1 102 } 103 104 r.cfg.Log("rolling back %s (current: v%d, target: v%d)", name, currentRelease.Version, previousVersion) 105 106 previousRelease, err := r.cfg.Releases.Get(name, previousVersion) 107 if err != nil { 108 return nil, nil, err 109 } 110 111 // Store a new release object with previous release's configuration 112 targetRelease := &release.Release{ 113 Name: name, 114 Namespace: currentRelease.Namespace, 115 Chart: previousRelease.Chart, 116 Config: previousRelease.Config, 117 Info: &release.Info{ 118 FirstDeployed: currentRelease.Info.FirstDeployed, 119 LastDeployed: time.Now(), 120 Status: release.StatusPendingRollback, 121 Notes: previousRelease.Info.Notes, 122 // Because we lose the reference to previous version elsewhere, we set the 123 // message here, and only override it later if we experience failure. 124 Description: fmt.Sprintf("Rollback to %d", previousVersion), 125 }, 126 Version: currentRelease.Version + 1, 127 Manifest: previousRelease.Manifest, 128 Hooks: previousRelease.Hooks, 129 } 130 131 return currentRelease, targetRelease, nil 132 } 133 134 func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release) (*release.Release, error) { 135 136 if r.DryRun { 137 r.cfg.Log("dry run for %s", targetRelease.Name) 138 return targetRelease, nil 139 } 140 141 // pre-rollback hooks 142 if !r.DisableHooks { 143 if err := r.execHook(targetRelease.Hooks, hooks.PreRollback); err != nil { 144 return targetRelease, err 145 } 146 } else { 147 r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name) 148 } 149 150 cr := bytes.NewBufferString(currentRelease.Manifest) 151 tr := bytes.NewBufferString(targetRelease.Manifest) 152 153 if err := r.cfg.KubeClient.Update(cr, tr, r.Force, r.Recreate); err != nil { 154 msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) 155 r.cfg.Log("warning: %s", msg) 156 currentRelease.Info.Status = release.StatusSuperseded 157 targetRelease.Info.Status = release.StatusFailed 158 targetRelease.Info.Description = msg 159 r.cfg.recordRelease(currentRelease) 160 r.cfg.recordRelease(targetRelease) 161 return targetRelease, err 162 } 163 164 if r.Wait { 165 buf := bytes.NewBufferString(targetRelease.Manifest) 166 if err := r.cfg.KubeClient.Wait(buf, r.Timeout); err != nil { 167 targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) 168 r.cfg.recordRelease(currentRelease) 169 r.cfg.recordRelease(targetRelease) 170 return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name) 171 } 172 } 173 174 // post-rollback hooks 175 if !r.DisableHooks { 176 if err := r.execHook(targetRelease.Hooks, hooks.PostRollback); err != nil { 177 return targetRelease, err 178 } 179 } 180 181 deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) 182 if err != nil { 183 return nil, err 184 } 185 // Supersede all previous deployments, see issue #2941. 186 for _, rel := range deployed { 187 r.cfg.Log("superseding previous deployment %d", rel.Version) 188 rel.Info.Status = release.StatusSuperseded 189 r.cfg.recordRelease(rel) 190 } 191 192 targetRelease.Info.Status = release.StatusDeployed 193 194 return targetRelease, nil 195 } 196 197 // execHook executes all of the hooks for the given hook event. 198 func (r *Rollback) execHook(hs []*release.Hook, hook string) error { 199 timeout := r.Timeout 200 executingHooks := []*release.Hook{} 201 202 for _, h := range hs { 203 for _, e := range h.Events { 204 if string(e) == hook { 205 executingHooks = append(executingHooks, h) 206 } 207 } 208 } 209 210 sort.Sort(hookByWeight(executingHooks)) 211 212 for _, h := range executingHooks { 213 if err := deleteHookByPolicy(r.cfg, h, hooks.BeforeHookCreation); err != nil { 214 return err 215 } 216 217 b := bytes.NewBufferString(h.Manifest) 218 if err := r.cfg.KubeClient.Create(b); err != nil { 219 return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path) 220 } 221 b.Reset() 222 b.WriteString(h.Manifest) 223 224 if err := r.cfg.KubeClient.WatchUntilReady(b, timeout); err != nil { 225 // If a hook is failed, checkout the annotation of the hook to determine whether the hook should be deleted 226 // under failed condition. If so, then clear the corresponding resource object in the hook 227 if err := deleteHookByPolicy(r.cfg, h, hooks.HookFailed); err != nil { 228 return err 229 } 230 return err 231 } 232 } 233 234 // If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted 235 // under succeeded condition. If so, then clear the corresponding resource object in each hook 236 for _, h := range executingHooks { 237 if err := deleteHookByPolicy(r.cfg, h, hooks.HookSucceeded); err != nil { 238 return err 239 } 240 h.LastRun = time.Now() 241 } 242 243 return nil 244 } 245 246 // deleteHookByPolicy deletes a hook if the hook policy instructs it to 247 func deleteHookByPolicy(cfg *Configuration, h *release.Hook, policy string) error { 248 if hookHasDeletePolicy(h, policy) { 249 b := bytes.NewBufferString(h.Manifest) 250 return cfg.KubeClient.Delete(b) 251 } 252 return nil 253 }