github.com/wangchanggan/helm@v0.0.0-20211020154240-11b1b7d5406d/pkg/tiller/release_install.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  	relutil "k8s.io/helm/pkg/releaseutil"
    30  	"k8s.io/helm/pkg/timeconv"
    31  )
    32  
    33  // InstallRelease installs a release and stores the release record.
    34  func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) {
    35  	s.Log("preparing install for %s", req.Name)
    36  	// 针对客户端传递过来的信息做准备,主要检查是否重名,然后对传递过来的各个参数和values.yaml进行渲染,然后拼接出Release对象。
    37  	rel, err := s.prepareRelease(req)
    38  	if err != nil {
    39  		s.Log("failed install prepare step: %s", err)
    40  		res := &services.InstallReleaseResponse{Release: rel}
    41  
    42  		// On dry run, append the manifest contents to a failed release. This is
    43  		// a stop-gap until we can revisit an error backchannel post-2.0.
    44  		// 如果是测试场景,就仅返回渲染失败的错误信息
    45  		if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") {
    46  			err = fmt.Errorf("%s\n%s", err, rel.Manifest)
    47  		}
    48  		return res, err
    49  	}
    50  
    51  	s.Log("performing install for %s", req.Name)
    52  	// 真正进行Release安装的函数
    53  	res, err := s.performRelease(rel, req)
    54  	if err != nil {
    55  		s.Log("failed install perform step: %s", err)
    56  	}
    57  	return res, err
    58  }
    59  
    60  // prepareRelease builds a release for an install operation.
    61  func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*release.Release, error) {
    62  	if req.Chart == nil {
    63  		return nil, errMissingChart
    64  	}
    65  
    66  	// 检查用户执行的Release名称是否唯一,如果是自动生成的,会自动保证该名称的唯一性
    67  	// 如果名称是用户指定的,这里会检查集群是否含有重名的Release
    68  	name, err := s.uniqName(req.Name, req.ReuseName)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	// 检查客户端和服务端之间的兼容性,判断客户端、服务端以及ApiServer是否兼容
    74  	caps, err := capabilities(s.clientset.Discovery())
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	// 每一个Release默认都有一个版本号,这里就是第一个版本号
    80  	revision := 1
    81  	ts := timeconv.Now()
    82  	options := chartutil.ReleaseOptions{
    83  		Name:      name,
    84  		Time:      ts,
    85  		Namespace: req.Namespace,
    86  		Revision:  revision,
    87  		IsInstall: true,
    88  	}
    89  	// 将传入的value进行渲染,组成新的values.yaml
    90  	valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	// 分离出安装资源、Hooks资源,以及将当前集群的ApiServer信息填入结构体,为下一步构造安装结构做铺垫
    96  	hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, req.SubNotes, caps.APIVersions)
    97  	if err != nil {
    98  		// Return a release with partial data so that client can show debugging
    99  		// information.
   100  		// 该结构体就是最终会存储的结构体,将需要安装的信息、Hooks信息和状态等内容进行初始化
   101  		rel := &release.Release{
   102  			Name:      name,
   103  			Namespace: req.Namespace,
   104  			Chart:     req.Chart,
   105  			Config:    req.Values,
   106  			Info: &release.Info{
   107  				FirstDeployed: ts,
   108  				LastDeployed:  ts,
   109  				Status:        &release.Status{Code: release.Status_UNKNOWN},
   110  				Description:   fmt.Sprintf("Install failed: %s", err),
   111  			},
   112  			Version: 0,
   113  		}
   114  		if manifestDoc != nil {
   115  			rel.Manifest = manifestDoc.String()
   116  		}
   117  		return rel, err
   118  	}
   119  
   120  	// Store a release.
   121  	rel := &release.Release{
   122  		Name:      name,
   123  		Namespace: req.Namespace,
   124  		Chart:     req.Chart,
   125  		Config:    req.Values,
   126  		Info: &release.Info{
   127  			FirstDeployed: ts,
   128  			LastDeployed:  ts,
   129  			Status:        &release.Status{Code: release.Status_PENDING_INSTALL},
   130  			Description:   "Initial install underway", // Will be overwritten.
   131  		},
   132  		Manifest: manifestDoc.String(),
   133  		Hooks:    hooks,
   134  		Version:  int32(revision),
   135  	}
   136  	if len(notesTxt) > 0 {
   137  		rel.Info.Status.Notes = notesTxt
   138  	}
   139  
   140  	return rel, nil
   141  }
   142  
   143  func hasCRDHook(hs []*release.Hook) bool {
   144  	for _, h := range hs {
   145  		for _, e := range h.Events {
   146  			if e == events[hooks.CRDInstall] {
   147  				return true
   148  			}
   149  		}
   150  	}
   151  	return false
   152  }
   153  
   154  // performRelease runs a release.
   155  // 安装环节
   156  // 首先要检查Chart是否含有一些Pre-hooks, 特别是crd-install这种Hooks
   157  // 因为针对这种类型的Hooks, Helm 会在创建其他资源之前,第一步优先创建该资源,否则后面依赖该资源的对象都会安装失败。
   158  func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) {
   159  	res := &services.InstallReleaseResponse{Release: r}
   160  	manifestDoc := []byte(r.Manifest)
   161  
   162  	if req.DryRun {
   163  		s.Log("dry run for %s", r.Name)
   164  
   165  		if !req.DisableCrdHook && hasCRDHook(r.Hooks) {
   166  			s.Log("validation skipped because CRD hook is present")
   167  			res.Release.Info.Description = "Validation skipped because CRDs are not installed"
   168  			return res, nil
   169  		}
   170  
   171  		// Here's the problem with dry runs and CRDs: We can't install a CRD
   172  		// during a dry run, which means it cannot be validated.
   173  		if err := validateManifest(s.env.KubeClient, req.Namespace, manifestDoc); err != nil {
   174  			return res, err
   175  		}
   176  
   177  		res.Release.Info.Description = "Dry run complete"
   178  		return res, nil
   179  	}
   180  
   181  	// crd-install hooks
   182  	if !req.DisableHooks && !req.DisableCrdHook {
   183  		if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.CRDInstall, req.Timeout); err != nil {
   184  			fmt.Printf("Finished installing CRD: %s", err)
   185  			return res, err
   186  		}
   187  	} else {
   188  		s.Log("CRD install hooks disabled for %s", req.Name)
   189  	}
   190  
   191  	// Because the CRDs are installed, they are used for validation during this step.
   192  	if err := validateManifest(s.env.KubeClient, req.Namespace, manifestDoc); err != nil {
   193  		return res, fmt.Errorf("validation failed: %s", err)
   194  	}
   195  
   196  	// pre-install hooks
   197  	if !req.DisableHooks {
   198  		if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil {
   199  			return res, err
   200  		}
   201  	} else {
   202  		s.Log("install hooks disabled for %s", req.Name)
   203  	}
   204  
   205  	switch h, err := s.env.Releases.History(req.Name); {
   206  	// if this is a replace operation, append to the release history
   207  	case req.ReuseName && err == nil && len(h) >= 1:
   208  		s.Log("name reuse for %s requested, replacing release", req.Name)
   209  		// get latest release revision
   210  		relutil.Reverse(h, relutil.SortByRevision)
   211  
   212  		// old release
   213  		old := h[0]
   214  
   215  		// update old release status
   216  		old.Info.Status.Code = release.Status_SUPERSEDED
   217  		s.recordRelease(old, true)
   218  
   219  		// update new release with next revision number
   220  		// so as to append to the old release's history
   221  		r.Version = old.Version + 1
   222  		updateReq := &services.UpdateReleaseRequest{
   223  			Wait:     req.Wait,
   224  			Recreate: false,
   225  			Timeout:  req.Timeout,
   226  		}
   227  		s.recordRelease(r, false)
   228  		if err := s.ReleaseModule.Update(old, r, updateReq, s.env); err != nil {
   229  			msg := fmt.Sprintf("Release replace %q failed: %s", r.Name, err)
   230  			s.Log("warning: %s", msg)
   231  			old.Info.Status.Code = release.Status_SUPERSEDED
   232  			r.Info.Status.Code = release.Status_FAILED
   233  			r.Info.Description = msg
   234  			s.recordRelease(old, true)
   235  			s.recordRelease(r, true)
   236  			return res, err
   237  		}
   238  
   239  	default:
   240  		// nothing to replace, create as normal
   241  		// regular manifests
   242  		s.recordRelease(r, false)
   243  		if err := s.ReleaseModule.Create(r, req, s.env); err != nil {
   244  			msg := fmt.Sprintf("Release %q failed: %s", r.Name, err)
   245  			s.Log("warning: %s", msg)
   246  			r.Info.Status.Code = release.Status_FAILED
   247  			r.Info.Description = msg
   248  			s.recordRelease(r, true)
   249  			return res, fmt.Errorf("release %s failed: %s", r.Name, err)
   250  		}
   251  	}
   252  
   253  	// post-install hooks
   254  	// 再次执行post-install hooks,也就是安装之后需要执行的Hooks
   255  	if !req.DisableHooks {
   256  		if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PostInstall, req.Timeout); err != nil {
   257  			msg := fmt.Sprintf("Release %q failed post-install: %s", r.Name, err)
   258  			s.Log("warning: %s", msg)
   259  			r.Info.Status.Code = release.Status_FAILED
   260  			r.Info.Description = msg
   261  			s.recordRelease(r, true)
   262  			return res, err
   263  		}
   264  	}
   265  
   266  	// 全部的创建流程就完成,将这个Release的状态改为Status_DEPLOYED。
   267  	r.Info.Status.Code = release.Status_DEPLOYED
   268  	if req.Description == "" {
   269  		r.Info.Description = "Install complete"
   270  	} else {
   271  		r.Info.Description = req.Description
   272  	}
   273  	// This is a tricky case. The release has been created, but the result
   274  	// cannot be recorded. The truest thing to tell the user is that the
   275  	// release was created. However, the user will not be able to do anything
   276  	// further with this release.
   277  	//
   278  	// One possible strategy would be to do a timed retry to see if we can get
   279  	// this stored in the future.
   280  	s.recordRelease(r, true)
   281  
   282  	return res, nil
   283  }