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