github.com/defensepoint-snyk-test/helm-new@v0.0.0-20211130153739-c57ea64d6603/cmd/helm/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 main
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"net/url"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  	"text/template"
    30  
    31  	"github.com/Masterminds/sprig"
    32  	"github.com/ghodss/yaml"
    33  	"github.com/spf13/cobra"
    34  
    35  	"k8s.io/apimachinery/pkg/util/validation"
    36  	"k8s.io/helm/pkg/chartutil"
    37  	"k8s.io/helm/pkg/downloader"
    38  	"k8s.io/helm/pkg/getter"
    39  	"k8s.io/helm/pkg/helm"
    40  	"k8s.io/helm/pkg/kube"
    41  	"k8s.io/helm/pkg/proto/hapi/release"
    42  	"k8s.io/helm/pkg/renderutil"
    43  	"k8s.io/helm/pkg/repo"
    44  	"k8s.io/helm/pkg/strvals"
    45  )
    46  
    47  const installDesc = `
    48  This command installs a chart archive.
    49  
    50  The install argument must be a chart reference, a path to a packaged chart,
    51  a path to an unpacked chart directory or a URL.
    52  
    53  To override values in a chart, use either the '--values' flag and pass in a file
    54  or use the '--set' flag and pass configuration from the command line.  To force string
    55  values in '--set', use '--set-string' instead. In case a value is large and therefore
    56  you want not to use neither '--values' nor '--set', use '--set-file' to read the
    57  single large value from file.
    58  
    59  	$ helm install -f myvalues.yaml ./redis
    60  
    61  or
    62  
    63  	$ helm install --set name=prod ./redis
    64  
    65  or
    66  
    67  	$ helm install --set-string long_int=1234567890 ./redis
    68  
    69  or
    70      $ helm install --set-file multiline_text=path/to/textfile
    71  
    72  You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
    73  last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
    74  contained a key called 'Test', the value set in override.yaml would take precedence:
    75  
    76  	$ helm install -f myvalues.yaml -f override.yaml ./redis
    77  
    78  You can specify the '--set' flag multiple times. The priority will be given to the
    79  last (right-most) set specified. For example, if both 'bar' and 'newbar' values are
    80  set for a key called 'foo', the 'newbar' value would take precedence:
    81  
    82  	$ helm install --set foo=bar --set foo=newbar ./redis
    83  
    84  
    85  To check the generated manifests of a release without installing the chart,
    86  the '--debug' and '--dry-run' flags can be combined. This will still require a
    87  round-trip to the Tiller server.
    88  
    89  If --verify is set, the chart MUST have a provenance file, and the provenance
    90  file MUST pass all verification steps.
    91  
    92  There are five different ways you can express the chart you want to install:
    93  
    94  1. By chart reference: helm install stable/mariadb
    95  2. By path to a packaged chart: helm install ./nginx-1.2.3.tgz
    96  3. By path to an unpacked chart directory: helm install ./nginx
    97  4. By absolute URL: helm install https://example.com/charts/nginx-1.2.3.tgz
    98  5. By chart reference and repo url: helm install --repo https://example.com/charts/ nginx
    99  
   100  CHART REFERENCES
   101  
   102  A chart reference is a convenient way of reference a chart in a chart repository.
   103  
   104  When you use a chart reference with a repo prefix ('stable/mariadb'), Helm will look in the local
   105  configuration for a chart repository named 'stable', and will then look for a
   106  chart in that repository whose name is 'mariadb'. It will install the latest
   107  version of that chart unless you also supply a version number with the
   108  '--version' flag.
   109  
   110  To see the list of chart repositories, use 'helm repo list'. To search for
   111  charts in a repository, use 'helm search'.
   112  `
   113  
   114  type installCmd struct {
   115  	name           string
   116  	namespace      string
   117  	valueFiles     valueFiles
   118  	chartPath      string
   119  	dryRun         bool
   120  	disableHooks   bool
   121  	disableCRDHook bool
   122  	replace        bool
   123  	verify         bool
   124  	keyring        string
   125  	out            io.Writer
   126  	client         helm.Interface
   127  	values         []string
   128  	stringValues   []string
   129  	fileValues     []string
   130  	nameTemplate   string
   131  	version        string
   132  	timeout        int64
   133  	wait           bool
   134  	repoURL        string
   135  	username       string
   136  	password       string
   137  	devel          bool
   138  	depUp          bool
   139  	description    string
   140  
   141  	certFile string
   142  	keyFile  string
   143  	caFile   string
   144  }
   145  
   146  type valueFiles []string
   147  
   148  func (v *valueFiles) String() string {
   149  	return fmt.Sprint(*v)
   150  }
   151  
   152  func (v *valueFiles) Type() string {
   153  	return "valueFiles"
   154  }
   155  
   156  func (v *valueFiles) Set(value string) error {
   157  	for _, filePath := range strings.Split(value, ",") {
   158  		*v = append(*v, filePath)
   159  	}
   160  	return nil
   161  }
   162  
   163  func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
   164  	inst := &installCmd{
   165  		out:    out,
   166  		client: c,
   167  	}
   168  
   169  	cmd := &cobra.Command{
   170  		Use:     "install [CHART]",
   171  		Short:   "install a chart archive",
   172  		Long:    installDesc,
   173  		PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
   174  		RunE: func(cmd *cobra.Command, args []string) error {
   175  			if err := checkArgsLength(len(args), "chart name"); err != nil {
   176  				return err
   177  			}
   178  
   179  			debug("Original chart version: %q", inst.version)
   180  			if inst.version == "" && inst.devel {
   181  				debug("setting version to >0.0.0-0")
   182  				inst.version = ">0.0.0-0"
   183  			}
   184  
   185  			cp, err := locateChartPath(inst.repoURL, inst.username, inst.password, args[0], inst.version, inst.verify, inst.keyring,
   186  				inst.certFile, inst.keyFile, inst.caFile)
   187  			if err != nil {
   188  				return err
   189  			}
   190  			inst.chartPath = cp
   191  			inst.client = ensureHelmClient(inst.client)
   192  			return inst.run()
   193  		},
   194  	}
   195  
   196  	f := cmd.Flags()
   197  	settings.AddFlagsTLS(f)
   198  	f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)")
   199  	f.StringVarP(&inst.name, "name", "n", "", "release name. If unspecified, it will autogenerate one for you")
   200  	f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into. Defaults to the current kube config namespace.")
   201  	f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install")
   202  	f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install")
   203  	f.BoolVar(&inst.disableCRDHook, "no-crd-hook", false, "prevent CRD hooks from running, but run other hooks")
   204  	f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
   205  	f.StringArrayVar(&inst.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
   206  	f.StringArrayVar(&inst.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
   207  	f.StringArrayVar(&inst.fileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
   208  	f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release")
   209  	f.BoolVar(&inst.verify, "verify", false, "verify the package before installing it")
   210  	f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification")
   211  	f.StringVar(&inst.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed")
   212  	f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
   213  	f.BoolVar(&inst.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
   214  	f.StringVar(&inst.repoURL, "repo", "", "chart repository url where to locate the requested chart")
   215  	f.StringVar(&inst.username, "username", "", "chart repository username where to locate the requested chart")
   216  	f.StringVar(&inst.password, "password", "", "chart repository password where to locate the requested chart")
   217  	f.StringVar(&inst.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
   218  	f.StringVar(&inst.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
   219  	f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
   220  	f.BoolVar(&inst.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
   221  	f.BoolVar(&inst.depUp, "dep-up", false, "run helm dependency update before installing the chart")
   222  	f.StringVar(&inst.description, "description", "", "specify a description for the release")
   223  
   224  	// set defaults from environment
   225  	settings.InitTLS(f)
   226  
   227  	return cmd
   228  }
   229  
   230  func (i *installCmd) run() error {
   231  	debug("CHART PATH: %s\n", i.chartPath)
   232  
   233  	if i.namespace == "" {
   234  		i.namespace = defaultNamespace()
   235  	}
   236  
   237  	rawVals, err := vals(i.valueFiles, i.values, i.stringValues, i.fileValues, i.certFile, i.keyFile, i.caFile)
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	// If template is specified, try to run the template.
   243  	if i.nameTemplate != "" {
   244  		i.name, err = generateName(i.nameTemplate)
   245  		if err != nil {
   246  			return err
   247  		}
   248  		// Print the final name so the user knows what the final name of the release is.
   249  		fmt.Printf("FINAL NAME: %s\n", i.name)
   250  	}
   251  
   252  	if msgs := validation.IsDNS1123Label(i.name); i.name != "" && len(msgs) > 0 {
   253  		return fmt.Errorf("release name %s is not a valid DNS label: %s", i.name, strings.Join(msgs, ";"))
   254  	}
   255  
   256  	// Check chart requirements to make sure all dependencies are present in /charts
   257  	chartRequested, err := chartutil.Load(i.chartPath)
   258  	if err != nil {
   259  		return prettyError(err)
   260  	}
   261  
   262  	if req, err := chartutil.LoadRequirements(chartRequested); err == nil {
   263  		// If checkDependencies returns an error, we have unfulfilled dependencies.
   264  		// As of Helm 2.4.0, this is treated as a stopping condition:
   265  		// https://github.com/kubernetes/helm/issues/2209
   266  		if err := renderutil.CheckDependencies(chartRequested, req); err != nil {
   267  			if i.depUp {
   268  				man := &downloader.Manager{
   269  					Out:        i.out,
   270  					ChartPath:  i.chartPath,
   271  					HelmHome:   settings.Home,
   272  					Keyring:    defaultKeyring(),
   273  					SkipUpdate: false,
   274  					Getters:    getter.All(settings),
   275  				}
   276  				if err := man.Update(); err != nil {
   277  					return prettyError(err)
   278  				}
   279  
   280  				// Update all dependencies which are present in /charts.
   281  				chartRequested, err = chartutil.Load(i.chartPath)
   282  				if err != nil {
   283  					return prettyError(err)
   284  				}
   285  			} else {
   286  				return prettyError(err)
   287  			}
   288  
   289  		}
   290  	} else if err != chartutil.ErrRequirementsNotFound {
   291  		return fmt.Errorf("cannot load requirements: %v", err)
   292  	}
   293  
   294  	res, err := i.client.InstallReleaseFromChart(
   295  		chartRequested,
   296  		i.namespace,
   297  		helm.ValueOverrides(rawVals),
   298  		helm.ReleaseName(i.name),
   299  		helm.InstallDryRun(i.dryRun),
   300  		helm.InstallReuseName(i.replace),
   301  		helm.InstallDisableHooks(i.disableHooks),
   302  		helm.InstallDisableCRDHook(i.disableCRDHook),
   303  		helm.InstallTimeout(i.timeout),
   304  		helm.InstallWait(i.wait),
   305  		helm.InstallDescription(i.description))
   306  	if err != nil {
   307  		return prettyError(err)
   308  	}
   309  
   310  	rel := res.GetRelease()
   311  	if rel == nil {
   312  		return nil
   313  	}
   314  	i.printRelease(rel)
   315  
   316  	// If this is a dry run, we can't display status.
   317  	if i.dryRun {
   318  		// This is special casing to avoid breaking backward compatibility:
   319  		if res.Release.Info.Description != "Dry run complete" {
   320  			fmt.Fprintf(os.Stdout, "WARNING: %s\n", res.Release.Info.Description)
   321  		}
   322  		return nil
   323  	}
   324  
   325  	// Print the status like status command does
   326  	status, err := i.client.ReleaseStatus(rel.Name)
   327  	if err != nil {
   328  		return prettyError(err)
   329  	}
   330  	PrintStatus(i.out, status)
   331  	return nil
   332  }
   333  
   334  // Merges source and destination map, preferring values from the source map
   335  func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
   336  	for k, v := range src {
   337  		// If the key doesn't exist already, then just set the key to that value
   338  		if _, exists := dest[k]; !exists {
   339  			dest[k] = v
   340  			continue
   341  		}
   342  		nextMap, ok := v.(map[string]interface{})
   343  		// If it isn't another map, overwrite the value
   344  		if !ok {
   345  			dest[k] = v
   346  			continue
   347  		}
   348  		// Edge case: If the key exists in the destination, but isn't a map
   349  		destMap, isMap := dest[k].(map[string]interface{})
   350  		// If the source map has a map for this key, prefer it
   351  		if !isMap {
   352  			dest[k] = v
   353  			continue
   354  		}
   355  		// If we got to this point, it is a map in both, so merge them
   356  		dest[k] = mergeValues(destMap, nextMap)
   357  	}
   358  	return dest
   359  }
   360  
   361  // vals merges values from files specified via -f/--values and
   362  // directly via --set or --set-string or --set-file, marshaling them to YAML
   363  func vals(valueFiles valueFiles, values []string, stringValues []string, fileValues []string, CertFile, KeyFile, CAFile string) ([]byte, error) {
   364  	base := map[string]interface{}{}
   365  
   366  	// User specified a values files via -f/--values
   367  	for _, filePath := range valueFiles {
   368  		currentMap := map[string]interface{}{}
   369  
   370  		var bytes []byte
   371  		var err error
   372  		if strings.TrimSpace(filePath) == "-" {
   373  			bytes, err = ioutil.ReadAll(os.Stdin)
   374  		} else {
   375  			bytes, err = readFile(filePath, CertFile, KeyFile, CAFile)
   376  		}
   377  
   378  		if err != nil {
   379  			return []byte{}, err
   380  		}
   381  
   382  		if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
   383  			return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err)
   384  		}
   385  		// Merge with the previous map
   386  		base = mergeValues(base, currentMap)
   387  	}
   388  
   389  	// User specified a value via --set
   390  	for _, value := range values {
   391  		if err := strvals.ParseInto(value, base); err != nil {
   392  			return []byte{}, fmt.Errorf("failed parsing --set data: %s", err)
   393  		}
   394  	}
   395  
   396  	// User specified a value via --set-string
   397  	for _, value := range stringValues {
   398  		if err := strvals.ParseIntoString(value, base); err != nil {
   399  			return []byte{}, fmt.Errorf("failed parsing --set-string data: %s", err)
   400  		}
   401  	}
   402  
   403  	// User specified a value via --set-file
   404  	for _, value := range fileValues {
   405  		reader := func(rs []rune) (interface{}, error) {
   406  			bytes, err := readFile(string(rs), CertFile, KeyFile, CAFile)
   407  			return string(bytes), err
   408  		}
   409  		if err := strvals.ParseIntoFile(value, base, reader); err != nil {
   410  			return []byte{}, fmt.Errorf("failed parsing --set-file data: %s", err)
   411  		}
   412  	}
   413  
   414  	return yaml.Marshal(base)
   415  }
   416  
   417  // printRelease prints info about a release if the Debug is true.
   418  func (i *installCmd) printRelease(rel *release.Release) {
   419  	if rel == nil {
   420  		return
   421  	}
   422  	// TODO: Switch to text/template like everything else.
   423  	fmt.Fprintf(i.out, "NAME:   %s\n", rel.Name)
   424  	if settings.Debug {
   425  		printRelease(i.out, rel)
   426  	}
   427  }
   428  
   429  // locateChartPath looks for a chart directory in known places, and returns either the full path or an error.
   430  //
   431  // This does not ensure that the chart is well-formed; only that the requested filename exists.
   432  //
   433  // Order of resolution:
   434  // - current working directory
   435  // - if path is absolute or begins with '.', error out here
   436  // - chart repos in $HELM_HOME
   437  // - URL
   438  //
   439  // If 'verify' is true, this will attempt to also verify the chart.
   440  func locateChartPath(repoURL, username, password, name, version string, verify bool, keyring,
   441  	certFile, keyFile, caFile string) (string, error) {
   442  	name = strings.TrimSpace(name)
   443  	version = strings.TrimSpace(version)
   444  	if fi, err := os.Stat(name); err == nil {
   445  		abs, err := filepath.Abs(name)
   446  		if err != nil {
   447  			return abs, err
   448  		}
   449  		if verify {
   450  			if fi.IsDir() {
   451  				return "", errors.New("cannot verify a directory")
   452  			}
   453  			if _, err := downloader.VerifyChart(abs, keyring); err != nil {
   454  				return "", err
   455  			}
   456  		}
   457  		return abs, nil
   458  	}
   459  	if filepath.IsAbs(name) || strings.HasPrefix(name, ".") {
   460  		return name, fmt.Errorf("path %q not found", name)
   461  	}
   462  
   463  	crepo := filepath.Join(settings.Home.Repository(), name)
   464  	if _, err := os.Stat(crepo); err == nil {
   465  		return filepath.Abs(crepo)
   466  	}
   467  
   468  	dl := downloader.ChartDownloader{
   469  		HelmHome: settings.Home,
   470  		Out:      os.Stdout,
   471  		Keyring:  keyring,
   472  		Getters:  getter.All(settings),
   473  		Username: username,
   474  		Password: password,
   475  	}
   476  	if verify {
   477  		dl.Verify = downloader.VerifyAlways
   478  	}
   479  	if repoURL != "" {
   480  		chartURL, err := repo.FindChartInAuthRepoURL(repoURL, username, password, name, version,
   481  			certFile, keyFile, caFile, getter.All(settings))
   482  		if err != nil {
   483  			return "", err
   484  		}
   485  		name = chartURL
   486  	}
   487  
   488  	if _, err := os.Stat(settings.Home.Archive()); os.IsNotExist(err) {
   489  		os.MkdirAll(settings.Home.Archive(), 0744)
   490  	}
   491  
   492  	filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive())
   493  	if err == nil {
   494  		lname, err := filepath.Abs(filename)
   495  		if err != nil {
   496  			return filename, err
   497  		}
   498  		debug("Fetched %s to %s\n", name, filename)
   499  		return lname, nil
   500  	} else if settings.Debug {
   501  		return filename, err
   502  	}
   503  
   504  	return filename, fmt.Errorf("failed to download %q (hint: running `helm repo update` may help)", name)
   505  }
   506  
   507  func generateName(nameTemplate string) (string, error) {
   508  	t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate)
   509  	if err != nil {
   510  		return "", err
   511  	}
   512  	var b bytes.Buffer
   513  	err = t.Execute(&b, nil)
   514  	if err != nil {
   515  		return "", err
   516  	}
   517  	return b.String(), nil
   518  }
   519  
   520  func defaultNamespace() string {
   521  	if ns, _, err := kube.GetConfig(settings.KubeContext, settings.KubeConfig).Namespace(); err == nil {
   522  		return ns
   523  	}
   524  	return "default"
   525  }
   526  
   527  //readFile load a file from the local directory or a remote file with a url.
   528  func readFile(filePath, CertFile, KeyFile, CAFile string) ([]byte, error) {
   529  	u, _ := url.Parse(filePath)
   530  	p := getter.All(settings)
   531  
   532  	// FIXME: maybe someone handle other protocols like ftp.
   533  	getterConstructor, err := p.ByScheme(u.Scheme)
   534  
   535  	if err != nil {
   536  		return ioutil.ReadFile(filePath)
   537  	}
   538  
   539  	getter, err := getterConstructor(filePath, CertFile, KeyFile, CAFile)
   540  	if err != nil {
   541  		return []byte{}, err
   542  	}
   543  	data, err := getter.Get(filePath)
   544  	return data.Bytes(), err
   545  }