github.com/sgoings/helm@v2.0.0-alpha.2.0.20170406211108-734e92851ac3+incompatible/cmd/helm/package.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  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"syscall"
    27  
    28  	"github.com/Masterminds/semver"
    29  	"github.com/spf13/cobra"
    30  	"golang.org/x/crypto/ssh/terminal"
    31  
    32  	"k8s.io/helm/pkg/chartutil"
    33  	"k8s.io/helm/pkg/helm/helmpath"
    34  	"k8s.io/helm/pkg/proto/hapi/chart"
    35  	"k8s.io/helm/pkg/provenance"
    36  	"k8s.io/helm/pkg/repo"
    37  )
    38  
    39  const packageDesc = `
    40  This command packages a chart into a versioned chart archive file. If a path
    41  is given, this will look at that path for a chart (which must contain a
    42  Chart.yaml file) and then package that directory.
    43  
    44  If no path is given, this will look in the present working directory for a
    45  Chart.yaml file, and (if found) build the current directory into a chart.
    46  
    47  Versioned chart archives are used by Helm package repositories.
    48  `
    49  
    50  type packageCmd struct {
    51  	save        bool
    52  	sign        bool
    53  	path        string
    54  	key         string
    55  	keyring     string
    56  	version     string
    57  	destination string
    58  
    59  	out  io.Writer
    60  	home helmpath.Home
    61  }
    62  
    63  func newPackageCmd(out io.Writer) *cobra.Command {
    64  	pkg := &packageCmd{
    65  		out: out,
    66  	}
    67  
    68  	cmd := &cobra.Command{
    69  		Use:   "package [flags] [CHART_PATH] [...]",
    70  		Short: "package a chart directory into a chart archive",
    71  		Long:  packageDesc,
    72  		RunE: func(cmd *cobra.Command, args []string) error {
    73  			pkg.home = helmpath.Home(homePath())
    74  			if len(args) == 0 {
    75  				return fmt.Errorf("This command needs at least one argument, the path to the chart.")
    76  			}
    77  			if pkg.sign {
    78  				if pkg.key == "" {
    79  					return errors.New("--key is required for signing a package")
    80  				}
    81  				if pkg.keyring == "" {
    82  					return errors.New("--keyring is required for signing a package")
    83  				}
    84  			}
    85  			for i := 0; i < len(args); i++ {
    86  				pkg.path = args[i]
    87  				if err := pkg.run(cmd, args); err != nil {
    88  					return err
    89  				}
    90  			}
    91  			return nil
    92  		},
    93  	}
    94  
    95  	f := cmd.Flags()
    96  	f.BoolVar(&pkg.save, "save", true, "save packaged chart to local chart repository")
    97  	f.BoolVar(&pkg.sign, "sign", false, "use a PGP private key to sign this package")
    98  	f.StringVar(&pkg.key, "key", "", "name of the key to use when signing. Used if --sign is true")
    99  	f.StringVar(&pkg.keyring, "keyring", defaultKeyring(), "location of a public keyring")
   100  	f.StringVar(&pkg.version, "version", "", "set the version on the chart to this semver version")
   101  	f.StringVarP(&pkg.destination, "destination", "d", ".", "location to write the chart.")
   102  
   103  	return cmd
   104  }
   105  
   106  func (p *packageCmd) run(cmd *cobra.Command, args []string) error {
   107  	path, err := filepath.Abs(p.path)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	ch, err := chartutil.LoadDir(path)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	// If version is set, modify the version.
   118  	if len(p.version) != 0 {
   119  		if err := setVersion(ch, p.version); err != nil {
   120  			return err
   121  		}
   122  		if flagDebug {
   123  			fmt.Fprintf(p.out, "Setting version to %s", p.version)
   124  		}
   125  	}
   126  
   127  	if filepath.Base(path) != ch.Metadata.Name {
   128  		return fmt.Errorf("directory name (%s) and Chart.yaml name (%s) must match", filepath.Base(path), ch.Metadata.Name)
   129  	}
   130  
   131  	if reqs, err := chartutil.LoadRequirements(ch); err == nil {
   132  		checkDependencies(ch, reqs, p.out)
   133  	}
   134  
   135  	var dest string
   136  	if p.destination == "." {
   137  		// Save to the current working directory.
   138  		dest, err = os.Getwd()
   139  		if err != nil {
   140  			return err
   141  		}
   142  	} else {
   143  		// Otherwise save to set destination
   144  		dest = p.destination
   145  	}
   146  
   147  	name, err := chartutil.Save(ch, dest)
   148  	if err == nil && flagDebug {
   149  		fmt.Fprintf(p.out, "Saved %s to current directory\n", name)
   150  	}
   151  
   152  	// Save to $HELM_HOME/local directory. This is second, because we don't want
   153  	// the case where we saved here, but didn't save to the default destination.
   154  	if p.save {
   155  		lr := p.home.LocalRepository()
   156  		if err := repo.AddChartToLocalRepo(ch, lr); err != nil {
   157  			return err
   158  		} else if flagDebug {
   159  			fmt.Fprintf(p.out, "Saved %s to %s\n", name, lr)
   160  		}
   161  	}
   162  
   163  	if p.sign {
   164  		err = p.clearsign(name)
   165  	}
   166  
   167  	return err
   168  }
   169  
   170  func setVersion(ch *chart.Chart, ver string) error {
   171  	// Verify that version is a SemVer, and error out if it is not.
   172  	if _, err := semver.NewVersion(ver); err != nil {
   173  		return err
   174  	}
   175  
   176  	// Set the version field on the chart.
   177  	ch.Metadata.Version = ver
   178  	return nil
   179  }
   180  
   181  func (p *packageCmd) clearsign(filename string) error {
   182  	// Load keyring
   183  	signer, err := provenance.NewFromKeyring(p.keyring, p.key)
   184  	if err != nil {
   185  		return err
   186  	}
   187  
   188  	if err := signer.DecryptKey(promptUser); err != nil {
   189  		return err
   190  	}
   191  
   192  	sig, err := signer.ClearSign(filename)
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	if flagDebug {
   198  		fmt.Fprintln(p.out, sig)
   199  	}
   200  
   201  	return ioutil.WriteFile(filename+".prov", []byte(sig), 0755)
   202  }
   203  
   204  // promptUser implements provenance.PassphraseFetcher
   205  func promptUser(name string) ([]byte, error) {
   206  	fmt.Printf("Password for key %q >  ", name)
   207  	pw, err := terminal.ReadPassword(int(syscall.Stdin))
   208  	fmt.Println()
   209  	return pw, err
   210  }