github.com/aaronmell/helm@v3.0.0-beta.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 "time" 23 24 "github.com/pkg/errors" 25 26 "helm.sh/helm/pkg/release" 27 ) 28 29 // Rollback is the action for rolling back to a given release. 30 // 31 // It provides the implementation of 'helm rollback'. 32 type Rollback struct { 33 cfg *Configuration 34 35 Version int 36 Timeout time.Duration 37 Wait bool 38 DisableHooks bool 39 DryRun bool 40 Recreate bool // will (if true) recreate pods after a rollback. 41 Force bool // will (if true) force resource upgrade through uninstall/recreate if needed 42 } 43 44 // NewRollback creates a new Rollback object with the given configuration. 45 func NewRollback(cfg *Configuration) *Rollback { 46 return &Rollback{ 47 cfg: cfg, 48 } 49 } 50 51 // Run executes 'helm rollback' against the given release. 52 func (r *Rollback) Run(name string) error { 53 r.cfg.Log("preparing rollback of %s", name) 54 currentRelease, targetRelease, err := r.prepareRollback(name) 55 if err != nil { 56 return err 57 } 58 59 if !r.DryRun { 60 r.cfg.Log("creating rolled back release for %s", name) 61 if err := r.cfg.Releases.Create(targetRelease); err != nil { 62 return err 63 } 64 } 65 r.cfg.Log("performing rollback of %s", name) 66 if _, err := r.performRollback(currentRelease, targetRelease); err != nil { 67 return err 68 } 69 70 if !r.DryRun { 71 r.cfg.Log("updating status for rolled back release for %s", name) 72 if err := r.cfg.Releases.Update(targetRelease); err != nil { 73 return err 74 } 75 } 76 return nil 77 } 78 79 // prepareRollback finds the previous release and prepares a new release object with 80 // the previous release's configuration 81 func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, error) { 82 if err := validateReleaseName(name); err != nil { 83 return nil, nil, errors.Errorf("prepareRollback: Release name is invalid: %s", name) 84 } 85 86 if r.Version < 0 { 87 return nil, nil, errInvalidRevision 88 } 89 90 currentRelease, err := r.cfg.Releases.Last(name) 91 if err != nil { 92 return nil, nil, err 93 } 94 95 previousVersion := r.Version 96 if r.Version == 0 { 97 previousVersion = currentRelease.Version - 1 98 } 99 100 r.cfg.Log("rolling back %s (current: v%d, target: v%d)", name, currentRelease.Version, previousVersion) 101 102 previousRelease, err := r.cfg.Releases.Get(name, previousVersion) 103 if err != nil { 104 return nil, nil, err 105 } 106 107 // Store a new release object with previous release's configuration 108 targetRelease := &release.Release{ 109 Name: name, 110 Namespace: currentRelease.Namespace, 111 Chart: previousRelease.Chart, 112 Config: previousRelease.Config, 113 Info: &release.Info{ 114 FirstDeployed: currentRelease.Info.FirstDeployed, 115 LastDeployed: time.Now(), 116 Status: release.StatusPendingRollback, 117 Notes: previousRelease.Info.Notes, 118 // Because we lose the reference to previous version elsewhere, we set the 119 // message here, and only override it later if we experience failure. 120 Description: fmt.Sprintf("Rollback to %d", previousVersion), 121 }, 122 Version: currentRelease.Version + 1, 123 Manifest: previousRelease.Manifest, 124 Hooks: previousRelease.Hooks, 125 } 126 127 return currentRelease, targetRelease, nil 128 } 129 130 func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release) (*release.Release, error) { 131 if r.DryRun { 132 r.cfg.Log("dry run for %s", targetRelease.Name) 133 return targetRelease, nil 134 } 135 136 current, err := r.cfg.KubeClient.Build(bytes.NewBufferString(currentRelease.Manifest)) 137 if err != nil { 138 return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") 139 } 140 target, err := r.cfg.KubeClient.Build(bytes.NewBufferString(targetRelease.Manifest)) 141 if err != nil { 142 return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") 143 } 144 145 // pre-rollback hooks 146 if !r.DisableHooks { 147 if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.Timeout); err != nil { 148 return targetRelease, err 149 } 150 } else { 151 r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name) 152 } 153 154 results, err := r.cfg.KubeClient.Update(current, target, r.Force) 155 156 if err != nil { 157 msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) 158 r.cfg.Log("warning: %s", msg) 159 currentRelease.Info.Status = release.StatusSuperseded 160 targetRelease.Info.Status = release.StatusFailed 161 targetRelease.Info.Description = msg 162 r.cfg.recordRelease(currentRelease) 163 r.cfg.recordRelease(targetRelease) 164 return targetRelease, err 165 } 166 167 if r.Recreate { 168 // NOTE: Because this is not critical for a release to succeed, we just 169 // log if an error occurs and continue onward. If we ever introduce log 170 // levels, we should make these error level logs so users are notified 171 // that they'll need to go do the cleanup on their own 172 if err := recreate(r.cfg, results.Updated); err != nil { 173 r.cfg.Log(err.Error()) 174 } 175 } 176 177 if r.Wait { 178 if err := r.cfg.KubeClient.Wait(target, r.Timeout); err != nil { 179 targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) 180 r.cfg.recordRelease(currentRelease) 181 r.cfg.recordRelease(targetRelease) 182 return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name) 183 } 184 } 185 186 // post-rollback hooks 187 if !r.DisableHooks { 188 if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.Timeout); err != nil { 189 return targetRelease, err 190 } 191 } 192 193 deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) 194 if err != nil { 195 return nil, err 196 } 197 // Supersede all previous deployments, see issue #2941. 198 for _, rel := range deployed { 199 r.cfg.Log("superseding previous deployment %d", rel.Version) 200 rel.Info.Status = release.StatusSuperseded 201 r.cfg.recordRelease(rel) 202 } 203 204 targetRelease.Info.Status = release.StatusDeployed 205 206 return targetRelease, nil 207 }