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