github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/action/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 action
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"net/url"
    25  	"os"
    26  	"path"
    27  	"path/filepath"
    28  	"strings"
    29  	"sync"
    30  	"text/template"
    31  	"time"
    32  
    33  	"github.com/Masterminds/sprig/v3"
    34  	"github.com/pkg/errors"
    35  	v1 "k8s.io/api/core/v1"
    36  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    37  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    38  	"k8s.io/cli-runtime/pkg/resource"
    39  	"sigs.k8s.io/yaml"
    40  
    41  	"github.com/stefanmcshane/helm/pkg/chart"
    42  	"github.com/stefanmcshane/helm/pkg/chartutil"
    43  	"github.com/stefanmcshane/helm/pkg/cli"
    44  	"github.com/stefanmcshane/helm/pkg/downloader"
    45  	"github.com/stefanmcshane/helm/pkg/getter"
    46  	"github.com/stefanmcshane/helm/pkg/kube"
    47  	kubefake "github.com/stefanmcshane/helm/pkg/kube/fake"
    48  	"github.com/stefanmcshane/helm/pkg/postrender"
    49  	"github.com/stefanmcshane/helm/pkg/registry"
    50  	"github.com/stefanmcshane/helm/pkg/release"
    51  	"github.com/stefanmcshane/helm/pkg/releaseutil"
    52  	"github.com/stefanmcshane/helm/pkg/repo"
    53  	"github.com/stefanmcshane/helm/pkg/storage"
    54  	"github.com/stefanmcshane/helm/pkg/storage/driver"
    55  )
    56  
    57  // NOTESFILE_SUFFIX that we want to treat special. It goes through the templating engine
    58  // but it's not a yaml file (resource) hence can't have hooks, etc. And the user actually
    59  // wants to see this file after rendering in the status command. However, it must be a suffix
    60  // since there can be filepath in front of it.
    61  const notesFileSuffix = "NOTES.txt"
    62  
    63  const defaultDirectoryPermission = 0755
    64  
    65  // Install performs an installation operation.
    66  type Install struct {
    67  	cfg *Configuration
    68  
    69  	ChartPathOptions
    70  
    71  	ClientOnly               bool
    72  	CreateNamespace          bool
    73  	DryRun                   bool
    74  	DisableHooks             bool
    75  	Replace                  bool
    76  	Wait                     bool
    77  	WaitForJobs              bool
    78  	Devel                    bool
    79  	DependencyUpdate         bool
    80  	Timeout                  time.Duration
    81  	Namespace                string
    82  	ReleaseName              string
    83  	GenerateName             bool
    84  	NameTemplate             string
    85  	Description              string
    86  	OutputDir                string
    87  	Atomic                   bool
    88  	SkipCRDs                 bool
    89  	SubNotes                 bool
    90  	DisableOpenAPIValidation bool
    91  	IncludeCRDs              bool
    92  	// KubeVersion allows specifying a custom kubernetes version to use and
    93  	// APIVersions allows a manual set of supported API Versions to be passed
    94  	// (for things like templating). These are ignored if ClientOnly is false
    95  	KubeVersion *chartutil.KubeVersion
    96  	APIVersions chartutil.VersionSet
    97  	// Used by helm template to render charts with .Release.IsUpgrade. Ignored if Dry-Run is false
    98  	IsUpgrade bool
    99  	// Used by helm template to add the release as part of OutputDir path
   100  	// OutputDir/<ReleaseName>
   101  	UseReleaseName bool
   102  	PostRenderer   postrender.PostRenderer
   103  	// Lock to control raceconditions when the process receives a SIGTERM
   104  	Lock sync.Mutex
   105  }
   106  
   107  // ChartPathOptions captures common options used for controlling chart paths
   108  type ChartPathOptions struct {
   109  	CaFile                string // --ca-file
   110  	CertFile              string // --cert-file
   111  	KeyFile               string // --key-file
   112  	InsecureSkipTLSverify bool   // --insecure-skip-verify
   113  	Keyring               string // --keyring
   114  	Password              string // --password
   115  	PassCredentialsAll    bool   // --pass-credentials
   116  	RepoURL               string // --repo
   117  	Username              string // --username
   118  	Verify                bool   // --verify
   119  	Version               string // --version
   120  
   121  	// registryClient provides a registry client but is not added with
   122  	// options from a flag
   123  	registryClient *registry.Client
   124  }
   125  
   126  // NewInstall creates a new Install object with the given configuration.
   127  func NewInstall(cfg *Configuration) *Install {
   128  	in := &Install{
   129  		cfg: cfg,
   130  	}
   131  	in.ChartPathOptions.registryClient = cfg.RegistryClient
   132  
   133  	return in
   134  }
   135  
   136  func (i *Install) installCRDs(crds []chart.CRD) error {
   137  	// We do these one file at a time in the order they were read.
   138  	totalItems := []*resource.Info{}
   139  	for _, obj := range crds {
   140  		// Read in the resources
   141  		res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false)
   142  		if err != nil {
   143  			return errors.Wrapf(err, "failed to install CRD %s", obj.Name)
   144  		}
   145  
   146  		// Send them to Kube
   147  		if _, err := i.cfg.KubeClient.Create(res); err != nil {
   148  			// If the error is CRD already exists, continue.
   149  			if apierrors.IsAlreadyExists(err) {
   150  				crdName := res[0].Name
   151  				i.cfg.Log("CRD %s is already present. Skipping.", crdName)
   152  				continue
   153  			}
   154  			return errors.Wrapf(err, "failed to install CRD %s", obj.Name)
   155  		}
   156  		totalItems = append(totalItems, res...)
   157  	}
   158  	if len(totalItems) > 0 {
   159  		// Invalidate the local cache, since it will not have the new CRDs
   160  		// present.
   161  		discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient()
   162  		if err != nil {
   163  			return err
   164  		}
   165  		i.cfg.Log("Clearing discovery cache")
   166  		discoveryClient.Invalidate()
   167  		// Give time for the CRD to be recognized.
   168  
   169  		if err := i.cfg.KubeClient.Wait(totalItems, 60*time.Second); err != nil {
   170  			return err
   171  		}
   172  
   173  		// Make sure to force a rebuild of the cache.
   174  		discoveryClient.ServerGroups()
   175  	}
   176  	return nil
   177  }
   178  
   179  // Run executes the installation
   180  //
   181  // If DryRun is set to true, this will prepare the release, but not install it
   182  
   183  func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
   184  	ctx := context.Background()
   185  	return i.RunWithContext(ctx, chrt, vals)
   186  }
   187  
   188  // Run executes the installation with Context
   189  func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
   190  	// Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`)
   191  	if !i.ClientOnly {
   192  		if err := i.cfg.KubeClient.IsReachable(); err != nil {
   193  			return nil, err
   194  		}
   195  	}
   196  
   197  	if err := i.availableName(); err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	if err := chartutil.ProcessDependencies(chrt, vals); err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	// Pre-install anything in the crd/ directory. We do this before Helm
   206  	// contacts the upstream server and builds the capabilities object.
   207  	if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 {
   208  		// On dry run, bail here
   209  		if i.DryRun {
   210  			i.cfg.Log("WARNING: This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.")
   211  		} else if err := i.installCRDs(crds); err != nil {
   212  			return nil, err
   213  		}
   214  	}
   215  
   216  	if i.ClientOnly {
   217  		// Add mock objects in here so it doesn't use Kube API server
   218  		// NOTE(bacongobbler): used for `helm template`
   219  		i.cfg.Capabilities = chartutil.DefaultCapabilities.Copy()
   220  		if i.KubeVersion != nil {
   221  			i.cfg.Capabilities.KubeVersion = *i.KubeVersion
   222  		}
   223  		i.cfg.Capabilities.APIVersions = append(i.cfg.Capabilities.APIVersions, i.APIVersions...)
   224  		i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard}
   225  
   226  		mem := driver.NewMemory()
   227  		mem.SetNamespace(i.Namespace)
   228  		i.cfg.Releases = storage.Init(mem)
   229  	} else if !i.ClientOnly && len(i.APIVersions) > 0 {
   230  		i.cfg.Log("API Version list given outside of client only mode, this list will be ignored")
   231  	}
   232  
   233  	// Make sure if Atomic is set, that wait is set as well. This makes it so
   234  	// the user doesn't have to specify both
   235  	i.Wait = i.Wait || i.Atomic
   236  
   237  	caps, err := i.cfg.getCapabilities()
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	// special case for helm template --is-upgrade
   243  	isUpgrade := i.IsUpgrade && i.DryRun
   244  	options := chartutil.ReleaseOptions{
   245  		Name:      i.ReleaseName,
   246  		Namespace: i.Namespace,
   247  		Revision:  1,
   248  		IsInstall: !isUpgrade,
   249  		IsUpgrade: isUpgrade,
   250  	}
   251  	valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	rel := i.createRelease(chrt, vals)
   257  
   258  	var manifestDoc *bytes.Buffer
   259  	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)
   260  	// Even for errors, attach this if available
   261  	if manifestDoc != nil {
   262  		rel.Manifest = manifestDoc.String()
   263  	}
   264  	// Check error from render
   265  	if err != nil {
   266  		rel.SetStatus(release.StatusFailed, fmt.Sprintf("failed to render resource: %s", err.Error()))
   267  		// Return a release with partial data so that the client can show debugging information.
   268  		return rel, err
   269  	}
   270  
   271  	// Mark this release as in-progress
   272  	rel.SetStatus(release.StatusPendingInstall, "Initial install underway")
   273  
   274  	var toBeAdopted kube.ResourceList
   275  	resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation)
   276  	if err != nil {
   277  		return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest")
   278  	}
   279  
   280  	// It is safe to use "force" here because these are resources currently rendered by the chart.
   281  	err = resources.Visit(setMetadataVisitor(rel.Name, rel.Namespace, true))
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  
   286  	// Install requires an extra validation step of checking that resources
   287  	// don't already exist before we actually create resources. If we continue
   288  	// forward and create the release object with resources that already exist,
   289  	// we'll end up in a state where we will delete those resources upon
   290  	// deleting the release because the manifest will be pointing at that
   291  	// resource
   292  	if !i.ClientOnly && !isUpgrade && len(resources) > 0 {
   293  		toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace)
   294  		if err != nil {
   295  			return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install")
   296  		}
   297  	}
   298  
   299  	// Bail out here if it is a dry run
   300  	if i.DryRun {
   301  		rel.Info.Description = "Dry run complete"
   302  		return rel, nil
   303  	}
   304  
   305  	if i.CreateNamespace {
   306  		ns := &v1.Namespace{
   307  			TypeMeta: metav1.TypeMeta{
   308  				APIVersion: "v1",
   309  				Kind:       "Namespace",
   310  			},
   311  			ObjectMeta: metav1.ObjectMeta{
   312  				Name: i.Namespace,
   313  				Labels: map[string]string{
   314  					"name": i.Namespace,
   315  				},
   316  			},
   317  		}
   318  		buf, err := yaml.Marshal(ns)
   319  		if err != nil {
   320  			return nil, err
   321  		}
   322  		resourceList, err := i.cfg.KubeClient.Build(bytes.NewBuffer(buf), true)
   323  		if err != nil {
   324  			return nil, err
   325  		}
   326  		if _, err := i.cfg.KubeClient.Create(resourceList); err != nil && !apierrors.IsAlreadyExists(err) {
   327  			return nil, err
   328  		}
   329  	}
   330  
   331  	// If Replace is true, we need to supercede the last release.
   332  	if i.Replace {
   333  		if err := i.replaceRelease(rel); err != nil {
   334  			return nil, err
   335  		}
   336  	}
   337  
   338  	// Store the release in history before continuing (new in Helm 3). We always know
   339  	// that this is a create operation.
   340  	if err := i.cfg.Releases.Create(rel); err != nil {
   341  		// We could try to recover gracefully here, but since nothing has been installed
   342  		// yet, this is probably safer than trying to continue when we know storage is
   343  		// not working.
   344  		return rel, err
   345  	}
   346  	rChan := make(chan resultMessage)
   347  	doneChan := make(chan struct{})
   348  	defer close(doneChan)
   349  	go i.performInstall(rChan, rel, toBeAdopted, resources)
   350  	go i.handleContext(ctx, rChan, doneChan, rel)
   351  	result := <-rChan
   352  	//start preformInstall go routine
   353  	return result.r, result.e
   354  }
   355  
   356  func (i *Install) performInstall(c chan<- resultMessage, rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) {
   357  
   358  	// pre-install hooks
   359  	if !i.DisableHooks {
   360  		if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout); err != nil {
   361  			i.reportToRun(c, rel, fmt.Errorf("failed pre-install: %s", err))
   362  			return
   363  		}
   364  	}
   365  
   366  	// At this point, we can do the install. Note that before we were detecting whether to
   367  	// do an update, but it's not clear whether we WANT to do an update if the re-use is set
   368  	// to true, since that is basically an upgrade operation.
   369  	if len(toBeAdopted) == 0 && len(resources) > 0 {
   370  		if _, err := i.cfg.KubeClient.Create(resources); err != nil {
   371  			i.reportToRun(c, rel, err)
   372  			return
   373  		}
   374  	} else if len(resources) > 0 {
   375  		if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, false); err != nil {
   376  			i.reportToRun(c, rel, err)
   377  			return
   378  		}
   379  	}
   380  
   381  	if i.Wait {
   382  		if i.WaitForJobs {
   383  			if err := i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout); err != nil {
   384  				i.reportToRun(c, rel, err)
   385  				return
   386  			}
   387  		} else {
   388  			if err := i.cfg.KubeClient.Wait(resources, i.Timeout); err != nil {
   389  				i.reportToRun(c, rel, err)
   390  				return
   391  			}
   392  		}
   393  	}
   394  
   395  	if !i.DisableHooks {
   396  		if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout); err != nil {
   397  			i.reportToRun(c, rel, fmt.Errorf("failed post-install: %s", err))
   398  			return
   399  		}
   400  	}
   401  
   402  	if len(i.Description) > 0 {
   403  		rel.SetStatus(release.StatusDeployed, i.Description)
   404  	} else {
   405  		rel.SetStatus(release.StatusDeployed, "Install complete")
   406  	}
   407  
   408  	// This is a tricky case. The release has been created, but the result
   409  	// cannot be recorded. The truest thing to tell the user is that the
   410  	// release was created. However, the user will not be able to do anything
   411  	// further with this release.
   412  	//
   413  	// One possible strategy would be to do a timed retry to see if we can get
   414  	// this stored in the future.
   415  	if err := i.recordRelease(rel); err != nil {
   416  		i.cfg.Log("failed to record the release: %s", err)
   417  	}
   418  
   419  	i.reportToRun(c, rel, nil)
   420  }
   421  func (i *Install) handleContext(ctx context.Context, c chan<- resultMessage, done chan struct{}, rel *release.Release) {
   422  	select {
   423  	case <-ctx.Done():
   424  		err := ctx.Err()
   425  		i.reportToRun(c, rel, err)
   426  	case <-done:
   427  		return
   428  	}
   429  }
   430  func (i *Install) reportToRun(c chan<- resultMessage, rel *release.Release, err error) {
   431  	i.Lock.Lock()
   432  	if err != nil {
   433  		rel, err = i.failRelease(rel, err)
   434  	}
   435  	c <- resultMessage{r: rel, e: err}
   436  	i.Lock.Unlock()
   437  }
   438  func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) {
   439  	rel.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error()))
   440  	if i.Atomic {
   441  		i.cfg.Log("Install failed and atomic is set, uninstalling release")
   442  		uninstall := NewUninstall(i.cfg)
   443  		uninstall.DisableHooks = i.DisableHooks
   444  		uninstall.KeepHistory = false
   445  		uninstall.Timeout = i.Timeout
   446  		if _, uninstallErr := uninstall.Run(i.ReleaseName); uninstallErr != nil {
   447  			return rel, errors.Wrapf(uninstallErr, "an error occurred while uninstalling the release. original install error: %s", err)
   448  		}
   449  		return rel, errors.Wrapf(err, "release %s failed, and has been uninstalled due to atomic being set", i.ReleaseName)
   450  	}
   451  	i.recordRelease(rel) // Ignore the error, since we have another error to deal with.
   452  	return rel, err
   453  }
   454  
   455  // availableName tests whether a name is available
   456  //
   457  // Roughly, this will return an error if name is
   458  //
   459  //	- empty
   460  //	- too long
   461  //	- already in use, and not deleted
   462  //	- used by a deleted release, and i.Replace is false
   463  func (i *Install) availableName() error {
   464  	start := i.ReleaseName
   465  
   466  	if err := chartutil.ValidateReleaseName(start); err != nil {
   467  		return errors.Wrapf(err, "release name %q", start)
   468  	}
   469  	if i.DryRun {
   470  		return nil
   471  	}
   472  
   473  	h, err := i.cfg.Releases.History(start)
   474  	if err != nil || len(h) < 1 {
   475  		return nil
   476  	}
   477  	releaseutil.Reverse(h, releaseutil.SortByRevision)
   478  	rel := h[0]
   479  
   480  	if st := rel.Info.Status; i.Replace && (st == release.StatusUninstalled || st == release.StatusFailed) {
   481  		return nil
   482  	}
   483  	return errors.New("cannot re-use a name that is still in use")
   484  }
   485  
   486  // createRelease creates a new release object
   487  func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}) *release.Release {
   488  	ts := i.cfg.Now()
   489  	return &release.Release{
   490  		Name:      i.ReleaseName,
   491  		Namespace: i.Namespace,
   492  		Chart:     chrt,
   493  		Config:    rawVals,
   494  		Info: &release.Info{
   495  			FirstDeployed: ts,
   496  			LastDeployed:  ts,
   497  			Status:        release.StatusUnknown,
   498  		},
   499  		Version: 1,
   500  	}
   501  }
   502  
   503  // recordRelease with an update operation in case reuse has been set.
   504  func (i *Install) recordRelease(r *release.Release) error {
   505  	// This is a legacy function which has been reduced to a oneliner. Could probably
   506  	// refactor it out.
   507  	return i.cfg.Releases.Update(r)
   508  }
   509  
   510  // replaceRelease replaces an older release with this one
   511  //
   512  // This allows us to re-use names by superseding an existing release with a new one
   513  func (i *Install) replaceRelease(rel *release.Release) error {
   514  	hist, err := i.cfg.Releases.History(rel.Name)
   515  	if err != nil || len(hist) == 0 {
   516  		// No releases exist for this name, so we can return early
   517  		return nil
   518  	}
   519  
   520  	releaseutil.Reverse(hist, releaseutil.SortByRevision)
   521  	last := hist[0]
   522  
   523  	// Update version to the next available
   524  	rel.Version = last.Version + 1
   525  
   526  	// Do not change the status of a failed release.
   527  	if last.Info.Status == release.StatusFailed {
   528  		return nil
   529  	}
   530  
   531  	// For any other status, mark it as superseded and store the old record
   532  	last.SetStatus(release.StatusSuperseded, "superseded by new release")
   533  	return i.recordRelease(last)
   534  }
   535  
   536  // write the <data> to <output-dir>/<name>. <append> controls if the file is created or content will be appended
   537  func writeToFile(outputDir string, name string, data string, append bool) error {
   538  	outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator))
   539  
   540  	err := ensureDirectoryForFile(outfileName)
   541  	if err != nil {
   542  		return err
   543  	}
   544  
   545  	f, err := createOrOpenFile(outfileName, append)
   546  	if err != nil {
   547  		return err
   548  	}
   549  
   550  	defer f.Close()
   551  
   552  	_, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data))
   553  
   554  	if err != nil {
   555  		return err
   556  	}
   557  
   558  	fmt.Printf("wrote %s\n", outfileName)
   559  	return nil
   560  }
   561  
   562  func createOrOpenFile(filename string, append bool) (*os.File, error) {
   563  	if append {
   564  		return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600)
   565  	}
   566  	return os.Create(filename)
   567  }
   568  
   569  // check if the directory exists to create file. creates if don't exists
   570  func ensureDirectoryForFile(file string) error {
   571  	baseDir := path.Dir(file)
   572  	_, err := os.Stat(baseDir)
   573  	if err != nil && !os.IsNotExist(err) {
   574  		return err
   575  	}
   576  
   577  	return os.MkdirAll(baseDir, defaultDirectoryPermission)
   578  }
   579  
   580  // NameAndChart returns the name and chart that should be used.
   581  //
   582  // This will read the flags and handle name generation if necessary.
   583  func (i *Install) NameAndChart(args []string) (string, string, error) {
   584  	flagsNotSet := func() error {
   585  		if i.GenerateName {
   586  			return errors.New("cannot set --generate-name and also specify a name")
   587  		}
   588  		if i.NameTemplate != "" {
   589  			return errors.New("cannot set --name-template and also specify a name")
   590  		}
   591  		return nil
   592  	}
   593  
   594  	if len(args) > 2 {
   595  		return args[0], args[1], errors.Errorf("expected at most two arguments, unexpected arguments: %v", strings.Join(args[2:], ", "))
   596  	}
   597  
   598  	if len(args) == 2 {
   599  		return args[0], args[1], flagsNotSet()
   600  	}
   601  
   602  	if i.NameTemplate != "" {
   603  		name, err := TemplateName(i.NameTemplate)
   604  		return name, args[0], err
   605  	}
   606  
   607  	if i.ReleaseName != "" {
   608  		return i.ReleaseName, args[0], nil
   609  	}
   610  
   611  	if !i.GenerateName {
   612  		return "", args[0], errors.New("must either provide a name or specify --generate-name")
   613  	}
   614  
   615  	base := filepath.Base(args[0])
   616  	if base == "." || base == "" {
   617  		base = "chart"
   618  	}
   619  	// if present, strip out the file extension from the name
   620  	if idx := strings.Index(base, "."); idx != -1 {
   621  		base = base[0:idx]
   622  	}
   623  
   624  	return fmt.Sprintf("%s-%d", base, time.Now().Unix()), args[0], nil
   625  }
   626  
   627  // TemplateName renders a name template, returning the name or an error.
   628  func TemplateName(nameTemplate string) (string, error) {
   629  	if nameTemplate == "" {
   630  		return "", nil
   631  	}
   632  
   633  	t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate)
   634  	if err != nil {
   635  		return "", err
   636  	}
   637  	var b bytes.Buffer
   638  	if err := t.Execute(&b, nil); err != nil {
   639  		return "", err
   640  	}
   641  
   642  	return b.String(), nil
   643  }
   644  
   645  // CheckDependencies checks the dependencies for a chart.
   646  func CheckDependencies(ch *chart.Chart, reqs []*chart.Dependency) error {
   647  	var missing []string
   648  
   649  OUTER:
   650  	for _, r := range reqs {
   651  		for _, d := range ch.Dependencies() {
   652  			if d.Name() == r.Name {
   653  				continue OUTER
   654  			}
   655  		}
   656  		missing = append(missing, r.Name)
   657  	}
   658  
   659  	if len(missing) > 0 {
   660  		return errors.Errorf("found in Chart.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", "))
   661  	}
   662  	return nil
   663  }
   664  
   665  // LocateChart looks for a chart directory in known places, and returns either the full path or an error.
   666  //
   667  // This does not ensure that the chart is well-formed; only that the requested filename exists.
   668  //
   669  // Order of resolution:
   670  // - relative to current working directory
   671  // - if path is absolute or begins with '.', error out here
   672  // - URL
   673  //
   674  // If 'verify' was set on ChartPathOptions, this will attempt to also verify the chart.
   675  func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (string, error) {
   676  	// If there is no registry client and the name is in an OCI registry return
   677  	// an error and a lookup will not occur.
   678  	if registry.IsOCI(name) && c.registryClient == nil {
   679  		return "", fmt.Errorf("unable to lookup chart %q, missing registry client", name)
   680  	}
   681  
   682  	name = strings.TrimSpace(name)
   683  	version := strings.TrimSpace(c.Version)
   684  
   685  	if _, err := os.Stat(name); err == nil {
   686  		abs, err := filepath.Abs(name)
   687  		if err != nil {
   688  			return abs, err
   689  		}
   690  		if c.Verify {
   691  			if _, err := downloader.VerifyChart(abs, c.Keyring); err != nil {
   692  				return "", err
   693  			}
   694  		}
   695  		return abs, nil
   696  	}
   697  	if filepath.IsAbs(name) || strings.HasPrefix(name, ".") {
   698  		return name, errors.Errorf("path %q not found", name)
   699  	}
   700  
   701  	dl := downloader.ChartDownloader{
   702  		Out:     os.Stdout,
   703  		Keyring: c.Keyring,
   704  		Getters: getter.All(settings),
   705  		Options: []getter.Option{
   706  			getter.WithPassCredentialsAll(c.PassCredentialsAll),
   707  			getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile),
   708  			getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSverify),
   709  		},
   710  		RepositoryConfig: settings.RepositoryConfig,
   711  		RepositoryCache:  settings.RepositoryCache,
   712  		RegistryClient:   c.registryClient,
   713  	}
   714  
   715  	if registry.IsOCI(name) {
   716  		dl.Options = append(dl.Options, getter.WithRegistryClient(c.registryClient))
   717  	}
   718  
   719  	if c.Verify {
   720  		dl.Verify = downloader.VerifyAlways
   721  	}
   722  	if c.RepoURL != "" {
   723  		chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(c.RepoURL, c.Username, c.Password, name, version,
   724  			c.CertFile, c.KeyFile, c.CaFile, c.InsecureSkipTLSverify, c.PassCredentialsAll, getter.All(settings))
   725  		if err != nil {
   726  			return "", err
   727  		}
   728  		name = chartURL
   729  
   730  		// Only pass the user/pass on when the user has said to or when the
   731  		// location of the chart repo and the chart are the same domain.
   732  		u1, err := url.Parse(c.RepoURL)
   733  		if err != nil {
   734  			return "", err
   735  		}
   736  		u2, err := url.Parse(chartURL)
   737  		if err != nil {
   738  			return "", err
   739  		}
   740  
   741  		// Host on URL (returned from url.Parse) contains the port if present.
   742  		// This check ensures credentials are not passed between different
   743  		// services on different ports.
   744  		if c.PassCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) {
   745  			dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password))
   746  		} else {
   747  			dl.Options = append(dl.Options, getter.WithBasicAuth("", ""))
   748  		}
   749  	} else {
   750  		dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password))
   751  	}
   752  
   753  	if err := os.MkdirAll(settings.RepositoryCache, 0755); err != nil {
   754  		return "", err
   755  	}
   756  
   757  	filename, _, err := dl.DownloadTo(name, version, settings.RepositoryCache)
   758  	if err != nil {
   759  		return "", err
   760  	}
   761  
   762  	lname, err := filepath.Abs(filename)
   763  	if err != nil {
   764  		return filename, err
   765  	}
   766  	return lname, nil
   767  }