github.com/x-helm/helm@v3.0.0-beta.3+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 if err := r.cfg.KubeClient.IsReachable(); err != nil { 54 return err 55 } 56 57 r.cfg.Log("preparing rollback of %s", name) 58 currentRelease, targetRelease, err := r.prepareRollback(name) 59 if err != nil { 60 return err 61 } 62 63 if !r.DryRun { 64 r.cfg.Log("creating rolled back release for %s", name) 65 if err := r.cfg.Releases.Create(targetRelease); err != nil { 66 return err 67 } 68 } 69 r.cfg.Log("performing rollback of %s", name) 70 if _, err := r.performRollback(currentRelease, targetRelease); err != nil { 71 return err 72 } 73 74 if !r.DryRun { 75 r.cfg.Log("updating status for rolled back release for %s", name) 76 if err := r.cfg.Releases.Update(targetRelease); err != nil { 77 return err 78 } 79 } 80 return 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 if r.DryRun { 136 r.cfg.Log("dry run for %s", targetRelease.Name) 137 return targetRelease, nil 138 } 139 140 current, err := r.cfg.KubeClient.Build(bytes.NewBufferString(currentRelease.Manifest)) 141 if err != nil { 142 return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") 143 } 144 target, err := r.cfg.KubeClient.Build(bytes.NewBufferString(targetRelease.Manifest)) 145 if err != nil { 146 return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") 147 } 148 149 // pre-rollback hooks 150 if !r.DisableHooks { 151 if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.Timeout); err != nil { 152 return targetRelease, err 153 } 154 } else { 155 r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name) 156 } 157 158 results, err := r.cfg.KubeClient.Update(current, target, r.Force) 159 160 if err != nil { 161 msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) 162 r.cfg.Log("warning: %s", msg) 163 currentRelease.Info.Status = release.StatusSuperseded 164 targetRelease.Info.Status = release.StatusFailed 165 targetRelease.Info.Description = msg 166 r.cfg.recordRelease(currentRelease) 167 r.cfg.recordRelease(targetRelease) 168 return targetRelease, err 169 } 170 171 if r.Recreate { 172 // NOTE: Because this is not critical for a release to succeed, we just 173 // log if an error occurs and continue onward. If we ever introduce log 174 // levels, we should make these error level logs so users are notified 175 // that they'll need to go do the cleanup on their own 176 if err := recreate(r.cfg, results.Updated); err != nil { 177 r.cfg.Log(err.Error()) 178 } 179 } 180 181 if r.Wait { 182 if err := r.cfg.KubeClient.Wait(target, r.Timeout); err != nil { 183 targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) 184 r.cfg.recordRelease(currentRelease) 185 r.cfg.recordRelease(targetRelease) 186 return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name) 187 } 188 } 189 190 // post-rollback hooks 191 if !r.DisableHooks { 192 if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.Timeout); err != nil { 193 return targetRelease, err 194 } 195 } 196 197 deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) 198 if err != nil { 199 return nil, err 200 } 201 // Supersede all previous deployments, see issue #2941. 202 for _, rel := range deployed { 203 r.cfg.Log("superseding previous deployment %d", rel.Version) 204 rel.Info.Status = release.StatusSuperseded 205 r.cfg.recordRelease(rel) 206 } 207 208 targetRelease.Info.Status = release.StatusDeployed 209 210 return targetRelease, nil 211 }