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  }