github.com/danielqsj/helm@v2.0.0-alpha.4.0.20160908204436-976e0ba5199b+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/helm"
    36  	"k8s.io/helm/pkg/proto/hapi/release"
    37  	"k8s.io/helm/pkg/timeconv"
    38  )
    39  
    40  const installDesc = `
    41  This command installs a chart archive.
    42  
    43  The install argument must be either a relative path to a chart directory or the
    44  name of a chart in the current working directory.
    45  
    46  To override values in a chart, use either the '--values' flag and pass in a file
    47  or use the '--set' flag and pass configuration from the command line.
    48  
    49  	$ helm install -f myvalues.yaml redis
    50  
    51  or
    52  
    53  	$ helm install --set name=prod redis
    54  
    55  To check the generated manifests of a release without installing the chart,
    56  the '--debug' and '--dry-run' flags can be combined. This will still require a
    57  round-trip to the Tiller server.
    58  
    59  If --verify is set, the chart MUST have a provenance file, and the provenenace
    60  fall MUST pass all verification steps.
    61  `
    62  
    63  type installCmd struct {
    64  	name         string
    65  	namespace    string
    66  	valuesFile   string
    67  	chartPath    string
    68  	dryRun       bool
    69  	disableHooks bool
    70  	replace      bool
    71  	verify       bool
    72  	keyring      string
    73  	out          io.Writer
    74  	client       helm.Interface
    75  	values       *values
    76  	nameTemplate string
    77  }
    78  
    79  func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
    80  	inst := &installCmd{
    81  		out:    out,
    82  		client: c,
    83  		values: new(values),
    84  	}
    85  
    86  	cmd := &cobra.Command{
    87  		Use:               "install [CHART]",
    88  		Short:             "install a chart archive",
    89  		Long:              installDesc,
    90  		PersistentPreRunE: setupConnection,
    91  		RunE: func(cmd *cobra.Command, args []string) error {
    92  			if err := checkArgsLength(1, len(args), "chart name"); err != nil {
    93  				return err
    94  			}
    95  			cp, err := locateChartPath(args[0], inst.verify, inst.keyring)
    96  			if err != nil {
    97  				return err
    98  			}
    99  			inst.chartPath = cp
   100  			inst.client = ensureHelmClient(inst.client)
   101  			return inst.run()
   102  		},
   103  	}
   104  
   105  	f := cmd.Flags()
   106  	f.StringVarP(&inst.valuesFile, "values", "f", "", "specify values in a YAML file")
   107  	f.StringVarP(&inst.name, "name", "n", "", "the release name. If unspecified, it will autogenerate one for you")
   108  	// TODO use kubeconfig default
   109  	f.StringVar(&inst.namespace, "namespace", "default", "the namespace to install the release into")
   110  	f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install")
   111  	f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install")
   112  	f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
   113  	f.Var(inst.values, "set", "set values on the command line. Separate values with commas: key1=val1,key2=val2")
   114  	f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release")
   115  	f.BoolVar(&inst.verify, "verify", false, "verify the package before installing it")
   116  	f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification")
   117  	return cmd
   118  }
   119  
   120  func (i *installCmd) run() error {
   121  	if flagDebug {
   122  		fmt.Fprintf(i.out, "Chart path: %s\n", i.chartPath)
   123  	}
   124  
   125  	rawVals, err := i.vals()
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	// If template is specified, try to run the template.
   131  	if i.nameTemplate != "" {
   132  		i.name, err = generateName(i.nameTemplate)
   133  		if err != nil {
   134  			return err
   135  		}
   136  		// Print the final name so the user knows what the final name of the release is.
   137  		fmt.Printf("Final name: %s\n", i.name)
   138  	}
   139  
   140  	res, err := i.client.InstallRelease(
   141  		i.chartPath,
   142  		i.namespace,
   143  		helm.ValueOverrides(rawVals),
   144  		helm.ReleaseName(i.name),
   145  		helm.InstallDryRun(i.dryRun),
   146  		helm.InstallReuseName(i.replace),
   147  		helm.InstallDisableHooks(i.disableHooks))
   148  	if err != nil {
   149  		return prettyError(err)
   150  	}
   151  
   152  	rel := res.GetRelease()
   153  	if rel == nil {
   154  		return nil
   155  	}
   156  	i.printRelease(rel)
   157  
   158  	// If this is a dry run, we can't display status.
   159  	if i.dryRun {
   160  		return nil
   161  	}
   162  
   163  	// Print the status like status command does
   164  	status, err := i.client.ReleaseStatus(rel.Name)
   165  	if err != nil {
   166  		return prettyError(err)
   167  	}
   168  	PrintStatus(i.out, status)
   169  	return nil
   170  }
   171  
   172  func (i *installCmd) vals() ([]byte, error) {
   173  	var buffer bytes.Buffer
   174  
   175  	// User specified a values file via -f/--values
   176  	if i.valuesFile != "" {
   177  		bytes, err := ioutil.ReadFile(i.valuesFile)
   178  		if err != nil {
   179  			return []byte{}, err
   180  		}
   181  		buffer.Write(bytes)
   182  	}
   183  
   184  	// User specified value pairs via --set
   185  	// These override any values in the specified file
   186  	if len(i.values.pairs) > 0 {
   187  		bytes, err := i.values.yaml()
   188  		if err != nil {
   189  			return []byte{}, err
   190  		}
   191  		buffer.Write(bytes)
   192  	}
   193  
   194  	return buffer.Bytes(), nil
   195  }
   196  
   197  // printRelease prints info about a release if the flagDebug is true.
   198  func (i *installCmd) printRelease(rel *release.Release) {
   199  	if rel == nil {
   200  		return
   201  	}
   202  	// TODO: Switch to text/template like everything else.
   203  	if flagDebug {
   204  		fmt.Fprintf(i.out, "NAME:   %s\n", rel.Name)
   205  		fmt.Fprintf(i.out, "NAMESPACE:   %s\n", rel.Namespace)
   206  		fmt.Fprintf(i.out, "INFO:   %s %s\n", timeconv.String(rel.Info.LastDeployed), rel.Info.Status)
   207  		fmt.Fprintf(i.out, "CHART:  %s %s\n", rel.Chart.Metadata.Name, rel.Chart.Metadata.Version)
   208  		fmt.Fprintf(i.out, "MANIFEST: %s\n", rel.Manifest)
   209  	} else {
   210  		fmt.Fprintln(i.out, rel.Name)
   211  	}
   212  }
   213  
   214  // values represents the command-line value pairs
   215  type values struct {
   216  	pairs map[string]interface{}
   217  }
   218  
   219  func (v *values) yaml() ([]byte, error) {
   220  	return yaml.Marshal(v.pairs)
   221  }
   222  
   223  func (v *values) String() string {
   224  	out, _ := v.yaml()
   225  	return string(out)
   226  }
   227  
   228  func (v *values) Type() string {
   229  	// Added to pflags.Value interface, but not documented there.
   230  	return "struct"
   231  }
   232  
   233  func (v *values) Set(data string) error {
   234  	v.pairs = map[string]interface{}{}
   235  
   236  	items := strings.Split(data, ",")
   237  	for _, item := range items {
   238  		n, val := splitPair(item)
   239  		names := strings.Split(n, ".")
   240  		ln := len(names)
   241  		current := &v.pairs
   242  		for i := 0; i < ln; i++ {
   243  			if i+1 == ln {
   244  				// We're at the last element. Set it.
   245  				(*current)[names[i]] = val
   246  			} else {
   247  				//
   248  				if e, ok := (*current)[names[i]]; !ok {
   249  					m := map[string]interface{}{}
   250  					(*current)[names[i]] = m
   251  					current = &m
   252  				} else if m, ok := e.(map[string]interface{}); ok {
   253  					current = &m
   254  				}
   255  			}
   256  		}
   257  	}
   258  	return nil
   259  }
   260  
   261  func splitPair(item string) (name string, value interface{}) {
   262  	pair := strings.SplitN(item, "=", 2)
   263  	if len(pair) == 1 {
   264  		return pair[0], true
   265  	}
   266  	return pair[0], pair[1]
   267  }
   268  
   269  // locateChartPath looks for a chart directory in known places, and returns either the full path or an error.
   270  //
   271  // This does not ensure that the chart is well-formed; only that the requested filename exists.
   272  //
   273  // Order of resolution:
   274  // - current working directory
   275  // - if path is absolute or begins with '.', error out here
   276  // - chart repos in $HELM_HOME
   277  //
   278  // If 'verify' is true, this will attempt to also verify the chart.
   279  func locateChartPath(name string, verify bool, keyring string) (string, error) {
   280  	if fi, err := os.Stat(name); err == nil {
   281  		abs, err := filepath.Abs(name)
   282  		if err != nil {
   283  			return abs, err
   284  		}
   285  		if verify {
   286  			if fi.IsDir() {
   287  				return "", errors.New("cannot verify a directory")
   288  			}
   289  			if err := verifyChart(abs, keyring); err != nil {
   290  				return "", err
   291  			}
   292  		}
   293  		return abs, nil
   294  	}
   295  	if filepath.IsAbs(name) || strings.HasPrefix(name, ".") {
   296  		return name, fmt.Errorf("path %q not found", name)
   297  	}
   298  
   299  	crepo := filepath.Join(repositoryDirectory(), name)
   300  	if _, err := os.Stat(crepo); err == nil {
   301  		return filepath.Abs(crepo)
   302  	}
   303  
   304  	// Try fetching the chart from a remote repo into a tmpdir
   305  	origname := name
   306  	if filepath.Ext(name) != ".tgz" {
   307  		name += ".tgz"
   308  	}
   309  	if err := downloadChart(name, false, ".", verify, keyring); err == nil {
   310  		lname, err := filepath.Abs(filepath.Base(name))
   311  		if err != nil {
   312  			return lname, err
   313  		}
   314  		fmt.Printf("Fetched %s to %s\n", origname, lname)
   315  		return lname, nil
   316  	}
   317  
   318  	return name, fmt.Errorf("file %q not found", origname)
   319  }
   320  
   321  func generateName(nameTemplate string) (string, error) {
   322  	t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate)
   323  	if err != nil {
   324  		return "", err
   325  	}
   326  	var b bytes.Buffer
   327  	err = t.Execute(&b, nil)
   328  	if err != nil {
   329  		return "", err
   330  	}
   331  	return b.String(), nil
   332  }