github.com/latiif/helm@v2.15.0+incompatible/pkg/tiller/release_update.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 tiller
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	ctx "golang.org/x/net/context"
    24  
    25  	"k8s.io/helm/pkg/chartutil"
    26  	"k8s.io/helm/pkg/hooks"
    27  	"k8s.io/helm/pkg/proto/hapi/release"
    28  	"k8s.io/helm/pkg/proto/hapi/services"
    29  	"k8s.io/helm/pkg/timeconv"
    30  )
    31  
    32  // UpdateRelease takes an existing release and new information, and upgrades the release.
    33  func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
    34  	if err := validateReleaseName(req.Name); err != nil {
    35  		s.Log("updateRelease: Release name is invalid: %s", req.Name)
    36  		return nil, err
    37  	}
    38  	s.Log("preparing update for %s", req.Name)
    39  	currentRelease, updatedRelease, err := s.prepareUpdate(req)
    40  	if err != nil {
    41  		s.Log("failed to prepare update: %s", err)
    42  		if req.Force {
    43  			// Use the --force, Luke.
    44  			s.Log("performing force update for %s", req.Name)
    45  			return s.performUpdateForce(req)
    46  		}
    47  		return nil, err
    48  	}
    49  
    50  	if !req.DryRun {
    51  		s.Log("creating updated release for %s", req.Name)
    52  		if err := s.env.Releases.Create(updatedRelease); err != nil {
    53  			return nil, err
    54  		}
    55  	}
    56  
    57  	s.Log("performing update for %s", req.Name)
    58  	res, err := s.performUpdate(currentRelease, updatedRelease, req)
    59  	if err != nil {
    60  		return res, err
    61  	}
    62  
    63  	if !req.DryRun {
    64  		s.Log("updating status for updated release for %s", req.Name)
    65  		if err := s.env.Releases.Update(updatedRelease); err != nil {
    66  			return res, err
    67  		}
    68  	}
    69  
    70  	return res, nil
    71  }
    72  
    73  // prepareUpdate builds an updated release for an update operation.
    74  func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) {
    75  	if req.Chart == nil {
    76  		return nil, nil, errMissingChart
    77  	}
    78  
    79  	// finds the deployed release with the given name
    80  	currentRelease, err := s.env.Releases.Deployed(req.Name)
    81  	if err != nil {
    82  		return nil, nil, err
    83  	}
    84  
    85  	// determine if values will be reused
    86  	if err := s.reuseValues(req, currentRelease); err != nil {
    87  		return nil, nil, err
    88  	}
    89  
    90  	// finds the non-deleted release with the given name
    91  	lastRelease, err := s.env.Releases.Last(req.Name)
    92  	if err != nil {
    93  		return nil, nil, err
    94  	}
    95  
    96  	// Increment revision count. This is passed to templates, and also stored on
    97  	// the release object.
    98  	revision := lastRelease.Version + 1
    99  
   100  	ts := timeconv.Now()
   101  	options := chartutil.ReleaseOptions{
   102  		Name:      req.Name,
   103  		Time:      ts,
   104  		Namespace: currentRelease.Namespace,
   105  		IsUpgrade: true,
   106  		Revision:  int(revision),
   107  	}
   108  
   109  	caps, err := capabilities(s.clientset.Discovery())
   110  	if err != nil {
   111  		return nil, nil, err
   112  	}
   113  	valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps)
   114  	if err != nil {
   115  		return nil, nil, err
   116  	}
   117  
   118  	hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, req.SubNotes, caps.APIVersions)
   119  	if err != nil {
   120  		return nil, nil, err
   121  	}
   122  
   123  	// Store an updated release.
   124  	updatedRelease := &release.Release{
   125  		Name:      req.Name,
   126  		Namespace: currentRelease.Namespace,
   127  		Chart:     req.Chart,
   128  		Config:    req.Values,
   129  		Info: &release.Info{
   130  			FirstDeployed: currentRelease.Info.FirstDeployed,
   131  			LastDeployed:  ts,
   132  			Status:        &release.Status{Code: release.Status_PENDING_UPGRADE},
   133  			Description:   "Preparing upgrade", // This should be overwritten later.
   134  		},
   135  		Version:  revision,
   136  		Manifest: manifestDoc.String(),
   137  		Hooks:    hooks,
   138  	}
   139  
   140  	if len(notesTxt) > 0 {
   141  		updatedRelease.Info.Status.Notes = notesTxt
   142  	}
   143  	err = validateManifest(s.env.KubeClient, currentRelease.Namespace, manifestDoc.Bytes())
   144  	return currentRelease, updatedRelease, err
   145  }
   146  
   147  // performUpdateForce performs the same action as a `helm delete && helm install --replace`.
   148  func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
   149  	// find the last release with the given name
   150  	oldRelease, err := s.env.Releases.Last(req.Name)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	res := &services.UpdateReleaseResponse{}
   156  
   157  	newRelease, err := s.prepareRelease(&services.InstallReleaseRequest{
   158  		Chart:        req.Chart,
   159  		Values:       req.Values,
   160  		DryRun:       req.DryRun,
   161  		Name:         req.Name,
   162  		DisableHooks: req.DisableHooks,
   163  		Namespace:    oldRelease.Namespace,
   164  		ReuseName:    true,
   165  		Timeout:      req.Timeout,
   166  		Wait:         req.Wait,
   167  	})
   168  	if err != nil {
   169  		s.Log("failed update prepare step: %s", err)
   170  		// On dry run, append the manifest contents to a failed release. This is
   171  		// a stop-gap until we can revisit an error backchannel post-2.0.
   172  		if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") {
   173  			err = fmt.Errorf("%s\n%s", err, newRelease.Manifest)
   174  		}
   175  		return res, err
   176  	}
   177  
   178  	// update new release with next revision number so as to append to the old release's history
   179  	newRelease.Version = oldRelease.Version + 1
   180  	res.Release = newRelease
   181  
   182  	if req.DryRun {
   183  		s.Log("dry run for %s", newRelease.Name)
   184  		res.Release.Info.Description = "Dry run complete"
   185  		return res, nil
   186  	}
   187  
   188  	// From here on out, the release is considered to be in Status_DELETING or Status_DELETED
   189  	// state. There is no turning back.
   190  	oldRelease.Info.Status.Code = release.Status_DELETING
   191  	oldRelease.Info.Deleted = timeconv.Now()
   192  	oldRelease.Info.Description = "Deletion in progress (or silently failed)"
   193  	s.recordRelease(oldRelease, true)
   194  
   195  	// pre-delete hooks
   196  	if !req.DisableHooks {
   197  		if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PreDelete, req.Timeout); err != nil {
   198  			return res, err
   199  		}
   200  	} else {
   201  		s.Log("hooks disabled for %s", req.Name)
   202  	}
   203  
   204  	// delete manifests from the old release
   205  	_, errs := s.ReleaseModule.Delete(oldRelease, nil, s.env)
   206  
   207  	oldRelease.Info.Status.Code = release.Status_DELETED
   208  	oldRelease.Info.Description = "Deletion complete"
   209  	s.recordRelease(oldRelease, true)
   210  
   211  	if len(errs) > 0 {
   212  		es := make([]string, 0, len(errs))
   213  		for _, e := range errs {
   214  			s.Log("error: %v", e)
   215  			es = append(es, e.Error())
   216  		}
   217  		return res, fmt.Errorf("Upgrade --force successfully deleted the previous release, but encountered %d error(s) and cannot continue: %s", len(es), strings.Join(es, "; "))
   218  	}
   219  
   220  	// post-delete hooks
   221  	if !req.DisableHooks {
   222  		if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PostDelete, req.Timeout); err != nil {
   223  			return res, err
   224  		}
   225  	}
   226  
   227  	// pre-install hooks
   228  	if !req.DisableHooks {
   229  		if err := s.execHook(newRelease.Hooks, newRelease.Name, newRelease.Namespace, hooks.PreInstall, req.Timeout); err != nil {
   230  			return res, err
   231  		}
   232  	}
   233  
   234  	s.recordRelease(newRelease, false)
   235  	if err := s.ReleaseModule.Update(oldRelease, newRelease, req, s.env); err != nil {
   236  		msg := fmt.Sprintf("Upgrade %q failed: %s", newRelease.Name, err)
   237  		s.Log("warning: %s", msg)
   238  		newRelease.Info.Status.Code = release.Status_FAILED
   239  		newRelease.Info.Description = msg
   240  		s.recordRelease(newRelease, true)
   241  		return res, err
   242  	}
   243  
   244  	// post-install hooks
   245  	if !req.DisableHooks {
   246  		if err := s.execHook(newRelease.Hooks, newRelease.Name, newRelease.Namespace, hooks.PostInstall, req.Timeout); err != nil {
   247  			msg := fmt.Sprintf("Release %q failed post-install: %s", newRelease.Name, err)
   248  			s.Log("warning: %s", msg)
   249  			newRelease.Info.Status.Code = release.Status_FAILED
   250  			newRelease.Info.Description = msg
   251  			s.recordRelease(newRelease, true)
   252  			return res, err
   253  		}
   254  	}
   255  
   256  	newRelease.Info.Status.Code = release.Status_DEPLOYED
   257  	if req.Description == "" {
   258  		newRelease.Info.Description = "Upgrade complete"
   259  	} else {
   260  		newRelease.Info.Description = req.Description
   261  	}
   262  	s.recordRelease(newRelease, true)
   263  
   264  	return res, nil
   265  }
   266  
   267  func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
   268  	res := &services.UpdateReleaseResponse{Release: updatedRelease}
   269  
   270  	if req.DryRun {
   271  		s.Log("dry run for %s", updatedRelease.Name)
   272  		res.Release.Info.Description = "Dry run complete"
   273  		return res, nil
   274  	}
   275  
   276  	// pre-upgrade hooks
   277  	if !req.DisableHooks {
   278  		if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil {
   279  			return res, err
   280  		}
   281  	} else {
   282  		s.Log("update hooks disabled for %s", req.Name)
   283  	}
   284  	if err := s.ReleaseModule.Update(originalRelease, updatedRelease, req, s.env); err != nil {
   285  		msg := fmt.Sprintf("Upgrade %q failed: %s", updatedRelease.Name, err)
   286  		s.Log("warning: %s", msg)
   287  		updatedRelease.Info.Status.Code = release.Status_FAILED
   288  		updatedRelease.Info.Description = msg
   289  		s.recordRelease(originalRelease, true)
   290  		s.recordRelease(updatedRelease, true)
   291  		return res, err
   292  	}
   293  
   294  	// post-upgrade hooks
   295  	if !req.DisableHooks {
   296  		if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PostUpgrade, req.Timeout); err != nil {
   297  			return res, err
   298  		}
   299  	}
   300  
   301  	originalRelease.Info.Status.Code = release.Status_SUPERSEDED
   302  	s.recordRelease(originalRelease, true)
   303  
   304  	updatedRelease.Info.Status.Code = release.Status_DEPLOYED
   305  	if req.Description == "" {
   306  		updatedRelease.Info.Description = "Upgrade complete"
   307  	} else {
   308  		updatedRelease.Info.Description = req.Description
   309  	}
   310  
   311  	return res, nil
   312  }