github.com/azure-devops-engineer/helm@v3.0.0-alpha.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  	"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  
   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  	if r.Wait {
   165  		buf := bytes.NewBufferString(targetRelease.Manifest)
   166  		if err := r.cfg.KubeClient.Wait(buf, r.Timeout); err != nil {
   167  			targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error()))
   168  			r.cfg.recordRelease(currentRelease)
   169  			r.cfg.recordRelease(targetRelease)
   170  			return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name)
   171  		}
   172  	}
   173  
   174  	// post-rollback hooks
   175  	if !r.DisableHooks {
   176  		if err := r.execHook(targetRelease.Hooks, hooks.PostRollback); err != nil {
   177  			return targetRelease, err
   178  		}
   179  	}
   180  
   181  	deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	// Supersede all previous deployments, see issue #2941.
   186  	for _, rel := range deployed {
   187  		r.cfg.Log("superseding previous deployment %d", rel.Version)
   188  		rel.Info.Status = release.StatusSuperseded
   189  		r.cfg.recordRelease(rel)
   190  	}
   191  
   192  	targetRelease.Info.Status = release.StatusDeployed
   193  
   194  	return targetRelease, nil
   195  }
   196  
   197  // execHook executes all of the hooks for the given hook event.
   198  func (r *Rollback) execHook(hs []*release.Hook, hook string) error {
   199  	timeout := r.Timeout
   200  	executingHooks := []*release.Hook{}
   201  
   202  	for _, h := range hs {
   203  		for _, e := range h.Events {
   204  			if string(e) == hook {
   205  				executingHooks = append(executingHooks, h)
   206  			}
   207  		}
   208  	}
   209  
   210  	sort.Sort(hookByWeight(executingHooks))
   211  
   212  	for _, h := range executingHooks {
   213  		if err := deleteHookByPolicy(r.cfg, h, hooks.BeforeHookCreation); err != nil {
   214  			return err
   215  		}
   216  
   217  		b := bytes.NewBufferString(h.Manifest)
   218  		if err := r.cfg.KubeClient.Create(b); err != nil {
   219  			return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path)
   220  		}
   221  		b.Reset()
   222  		b.WriteString(h.Manifest)
   223  
   224  		if err := r.cfg.KubeClient.WatchUntilReady(b, timeout); err != nil {
   225  			// If a hook is failed, checkout the annotation of the hook to determine whether the hook should be deleted
   226  			// under failed condition. If so, then clear the corresponding resource object in the hook
   227  			if err := deleteHookByPolicy(r.cfg, h, hooks.HookFailed); err != nil {
   228  				return err
   229  			}
   230  			return err
   231  		}
   232  	}
   233  
   234  	// If all hooks are succeeded, checkout the annotation of each hook to determine whether the hook should be deleted
   235  	// under succeeded condition. If so, then clear the corresponding resource object in each hook
   236  	for _, h := range executingHooks {
   237  		if err := deleteHookByPolicy(r.cfg, h, hooks.HookSucceeded); err != nil {
   238  			return err
   239  		}
   240  		h.LastRun = time.Now()
   241  	}
   242  
   243  	return nil
   244  }
   245  
   246  // deleteHookByPolicy deletes a hook if the hook policy instructs it to
   247  func deleteHookByPolicy(cfg *Configuration, h *release.Hook, policy string) error {
   248  	if hookHasDeletePolicy(h, policy) {
   249  		b := bytes.NewBufferString(h.Manifest)
   250  		return cfg.KubeClient.Delete(b)
   251  	}
   252  	return nil
   253  }