github.com/migueleliasweb/helm@v2.6.1+incompatible/cmd/helm/install.go (about)

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