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  }