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  }