一,首先下载helm-controller源码
helm-controller
二,找到helm.go方法,这是一切的开始
func main() { //生成一个新的配置结构体 actionConfig := new(action.Configuration) //将所有命令集成进来,找到具体的命令,下文详说 cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:]) if err != nil { warning("%+v", err) os.Exit(1) } // 对配置进行初始化,填入数据,如storage等 cobra.OnInitialize(func() { helmDriver := os.Getenv("HELM_DRIVER") if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, debug); err != nil { log.Fatal(err) } if helmDriver == "memory" { loadReleasesInMemory(actionConfig) } }) //执行找到的命令 if err := cmd.Execute(); err != nil { debug("%+v", err) switch e := err.(type) { case pluginError: os.Exit(e.code) default: os.Exit(1) } } }
三, newRootCmd 方法
在root.go文件中,内容非常多,主要内容有两个。
1是解析命令行参数,如namespace,config context,将参数注册进命令中,以备执行时使用。
2是将子命令如install,list等注入到cmd中,这样具体的执行就进入了子命令
四,以install 子命令为例
helm\install.go 文件
func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { //这个action是参数传入的,即在main函数初始化好的,里面包含了指定的driver,storage等信息,这里生成具体实例 client := action.NewInstall(cfg) valueOpts := &values.Options{} var outfmt output.Format cmd := &cobra.Command{ Use: "install [NAME] [CHART]", Short: "install a chart", Long: installDesc, Args: require.MinimumNArgs(1), ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return compInstall(args, toComplete, client) }, //注入具体执行函数runInstall RunE: func(_ *cobra.Command, args []string) error { rel, err := runInstall(args, client, valueOpts, out) if err != nil { return err } return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false}) }, } addInstallFlags(cmd, cmd.Flags(), client, valueOpts) bindOutputFlag(cmd, &outfmt) bindPostRenderFlag(cmd, &client.PostRenderer) return cmd }
五,runInstall方法
func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) { debug("Original chart version: %q", client.Version) if client.Version == "" && client.Devel { debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } //解析参数 name, chart, err := client.NameAndChart(args) if err != nil { return nil, err } client.ReleaseName = name //下载chart ,返回文件路径 cp, err := client.ChartPathOptions.LocateChart(chart, settings) if err != nil { return nil, err } debug("CHART PATH: %s\n", cp) //生成values.yaml中的参数 p := getter.All(settings) vals, err := valueOpts.MergeValues(p) if err != nil { return nil, err } // 验证文件,并生成chart对象 chartRequested, err := loader.Load(cp) if err != nil { return nil, err } if err := checkIfInstallable(chartRequested); err != nil { return nil, err } if chartRequested.Metadata.Deprecated { warning("This chart is deprecated") } //处理依赖 if req := chartRequested.Metadata.Dependencies; req != nil { // If CheckDependencies returns an error, we have unfulfilled dependencies. // As of Helm 2.4.0, this is treated as a stopping condition: // https://github.com/helm/helm/issues/2209 if err := action.CheckDependencies(chartRequested, req); err != nil { if client.DependencyUpdate { man := &downloader.Manager{ Out: out, ChartPath: cp, Keyring: client.ChartPathOptions.Keyring, SkipUpdate: false, Getters: p, RepositoryConfig: settings.RepositoryConfig, RepositoryCache: settings.RepositoryCache, Debug: settings.Debug, } if err := man.Update(); err != nil { return nil, err } // Reload the chart with the updated Chart.lock file. if chartRequested, err = loader.Load(cp); err != nil { return nil, errors.Wrap(err, "failed reloading chart after repo update") } } else { return nil, err } } } client.Namespace = settings.Namespace() //将charts 和 values参数传入,执行安装。这里使用的action.Install return client.Run(chartRequested, vals) }
六,action.Install 中的run方法
在action\install.go 里
func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { // Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`) if !i.ClientOnly { if err := i.cfg.KubeClient.IsReachable(); err != nil { return nil, err } } if err := i.availableName(); err != nil { return nil, err } //处理并安装crds // Pre-install anything in the crd/ directory. We do this before Helm // contacts the upstream server and builds the capabilities object. if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 { // On dry run, bail here if i.DryRun { i.cfg.Log("WARNING: This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") } else if err := i.installCRDs(crds); err != nil { return nil, err } } if i.ClientOnly { // Add mock objects in here so it doesn't use Kube API server // NOTE(bacongobbler): used for `helm template` i.cfg.Capabilities = chartutil.DefaultCapabilities i.cfg.Capabilities.APIVersions = append(i.cfg.Capabilities.APIVersions, i.APIVersions...) i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard} mem := driver.NewMemory() mem.SetNamespace(i.Namespace) //初始化storage i.cfg.Releases = storage.Init(mem) } else if !i.ClientOnly && len(i.APIVersions) > 0 { i.cfg.Log("API Version list given outside of client only mode, this list will be ignored") } //处理依赖 if err := chartutil.ProcessDependencies(chrt, vals); err != nil { return nil, err } // Make sure if Atomic is set, that wait is set as well. This makes it so // the user doesn't have to specify both i.Wait = i.Wait || i.Atomic caps, err := i.cfg.getCapabilities() if err != nil { return nil, err } //special case for helm template --is-upgrade isUpgrade := i.IsUpgrade && i.DryRun options := chartutil.ReleaseOptions{ Name: i.ReleaseName, Namespace: i.Namespace, Revision: 1, IsInstall: !isUpgrade, IsUpgrade: isUpgrade, } valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps) if err != nil { return nil, err } //生成release记录 rel := i.createRelease(chrt, vals) var manifestDoc *bytes.Buffer rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun) // Even for errors, attach this if available if manifestDoc != nil { rel.Manifest = manifestDoc.String() } // Check error from render if err != nil { rel.SetStatus(release.StatusFailed, fmt.Sprintf("failed to render resource: %s", err.Error())) // Return a release with partial data so that the client can show debugging information. return rel, err } // Mark this release as in-progress rel.SetStatus(release.StatusPendingInstall, "Initial install underway") var toBeAdopted kube.ResourceList resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation) if err != nil { return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") } // It is safe to use "force" here because these are resources currently rendered by the chart. err = resources.Visit(setMetadataVisitor(rel.Name, rel.Namespace, true)) if err != nil { return nil, err } // Install requires an extra validation step of checking that resources // don't already exist before we actually create resources. If we continue // forward and create the release object with resources that already exist, // we'll end up in a state where we will delete those resources upon // deleting the release because the manifest will be pointing at that // resource if !i.ClientOnly && !isUpgrade && len(resources) > 0 { toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace) if err != nil { return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install") } } // Bail out here if it is a dry run if i.DryRun { rel.Info.Description = "Dry run complete" return rel, nil } if i.CreateNamespace { ns := &v1.Namespace{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", Kind: "Namespace", }, ObjectMeta: metav1.ObjectMeta{ Name: i.Namespace, Labels: map[string]string{ "name": i.Namespace, }, }, } buf, err := yaml.Marshal(ns) if err != nil { return nil, err } resourceList, err := i.cfg.KubeClient.Build(bytes.NewBuffer(buf), true) if err != nil { return nil, err } if _, err := i.cfg.KubeClient.Create(resourceList); err != nil && !apierrors.IsAlreadyExists(err) { return nil, err } } // If Replace is true, we need to supercede the last release. if i.Replace { if err := i.replaceRelease(rel); err != nil { return nil, err } } // Store the release in history before continuing (new in Helm 3). We always know // that this is a create operation. if err := i.cfg.Releases.Create(rel); err != nil { // We could try to recover gracefully here, but since nothing has been installed // yet, this is probably safer than trying to continue when we know storage is // not working. return rel, err } // pre-install hooks if !i.DisableHooks { if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout); err != nil { return i.failRelease(rel, fmt.Errorf("failed pre-install: %s", err)) } } // At this point, we can do the install. Note that before we were detecting whether to // do an update, but it's not clear whether we WANT to do an update if the re-use is set // to true, since that is basically an upgrade operation. if len(toBeAdopted) == 0 && len(resources) > 0 { if _, err := i.cfg.KubeClient.Create(resources); err != nil { return i.failRelease(rel, err) } } else if len(resources) > 0 { if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, false); err != nil { return i.failRelease(rel, err) } } if i.Wait { if i.WaitForJobs { if err := i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout); err != nil { return i.failRelease(rel, err) } } else { if err := i.cfg.KubeClient.Wait(resources, i.Timeout); err != nil { return i.failRelease(rel, err) } } } if !i.DisableHooks { if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout); err != nil { return i.failRelease(rel, fmt.Errorf("failed post-install: %s", err)) } } if len(i.Description) > 0 { rel.SetStatus(release.StatusDeployed, i.Description) } else { rel.SetStatus(release.StatusDeployed, "Install complete") } // This is a tricky case. The release has been created, but the result // cannot be recorded. The truest thing to tell the user is that the // release was created. However, the user will not be able to do anything // further with this release. // // One possible strategy would be to do a timed retry to see if we can get // this stored in the future. if err := i.recordRelease(rel); err != nil { i.cfg.Log("failed to record the release: %s", err) } return rel, nil }