github.com/umeshredd/helm@v3.0.0-alpha.1+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 // TODO add wait 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 // post-rollback hooks 165 if !r.DisableHooks { 166 if err := r.execHook(targetRelease.Hooks, hooks.PostRollback); err != nil { 167 return targetRelease, err 168 } 169 } 170 171 deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) 172 if err != nil { 173 return nil, err 174 } 175 // Supersede all previous deployments, see issue #2941. 176 for _, rel := range deployed { 177 r.cfg.Log("superseding previous deployment %d", rel.Version) 178 rel.Info.Status = release.StatusSuperseded 179 r.cfg.recordRelease(rel) 180 } 181 182 targetRelease.Info.Status = release.StatusDeployed 183 184 return targetRelease, nil 185 } 186 187 // execHook executes all of the hooks for the given hook event. 188 func (r *Rollback) execHook(hs []*release.Hook, hook string) error { 189 timeout := r.Timeout 190 executingHooks := []*release.Hook{} 191 192 for _, h := range hs { 193 for _, e := range h.Events { 194 if string(e) == hook { 195 executingHooks = append(executingHooks, h) 196 } 197 } 198 } 199 200 sort.Sort(hookByWeight(executingHooks)) 201 202 for _, h := range executingHooks { 203 if err := deleteHookByPolicy(r.cfg, h, hooks.BeforeHookCreation); err != nil { 204 return err 205 } 206 207 b := bytes.NewBufferString(h.Manifest) 208 if err := r.cfg.KubeClient.Create(b); err != nil { 209 return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path) 210 } 211 b.Reset() 212 b.WriteString(h.Manifest) 213 214 if err := r.cfg.KubeClient.WatchUntilReady(b, timeout); err != nil { 215 // If a hook is failed, checkout the annotation of the hook to determine whether the hook should be deleted 216 // under failed condition. If so, then clear the corresponding resource object in the hook 217 if err := deleteHookByPolicy(r.cfg, h, hooks.HookFailed); err != nil { 218 return err 219 } 220 return err 221 } 222 } 223 224 // If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted 225 // under succeeded condition. If so, then clear the corresponding resource object in each hook 226 for _, h := range executingHooks { 227 if err := deleteHookByPolicy(r.cfg, h, hooks.HookSucceeded); err != nil { 228 return err 229 } 230 h.LastRun = time.Now() 231 } 232 233 return nil 234 } 235 236 // deleteHookByPolicy deletes a hook if the hook policy instructs it to 237 func deleteHookByPolicy(cfg *Configuration, h *release.Hook, policy string) error { 238 if hookHasDeletePolicy(h, policy) { 239 b := bytes.NewBufferString(h.Manifest) 240 return cfg.KubeClient.Delete(b) 241 } 242 return nil 243 }