github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/action/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 action
    18  
    19  import (
    20  	"bufio"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"syscall"
    25  
    26  	"github.com/Masterminds/semver/v3"
    27  	"github.com/pkg/errors"
    28  	"golang.org/x/term"
    29  
    30  	"github.com/stefanmcshane/helm/pkg/chart/loader"
    31  	"github.com/stefanmcshane/helm/pkg/chartutil"
    32  	"github.com/stefanmcshane/helm/pkg/provenance"
    33  )
    34  
    35  // Package is the action for packaging a chart.
    36  //
    37  // It provides the implementation of 'helm package'.
    38  type Package struct {
    39  	Sign             bool
    40  	Key              string
    41  	Keyring          string
    42  	PassphraseFile   string
    43  	Version          string
    44  	AppVersion       string
    45  	Destination      string
    46  	DependencyUpdate bool
    47  
    48  	RepositoryConfig string
    49  	RepositoryCache  string
    50  }
    51  
    52  // NewPackage creates a new Package object with the given configuration.
    53  func NewPackage() *Package {
    54  	return &Package{}
    55  }
    56  
    57  // Run executes 'helm package' against the given chart and returns the path to the packaged chart.
    58  func (p *Package) Run(path string, vals map[string]interface{}) (string, error) {
    59  	ch, err := loader.LoadDir(path)
    60  	if err != nil {
    61  		return "", err
    62  	}
    63  
    64  	// If version is set, modify the version.
    65  	if p.Version != "" {
    66  		ch.Metadata.Version = p.Version
    67  	}
    68  
    69  	if err := validateVersion(ch.Metadata.Version); err != nil {
    70  		return "", err
    71  	}
    72  
    73  	if p.AppVersion != "" {
    74  		ch.Metadata.AppVersion = p.AppVersion
    75  	}
    76  
    77  	if reqs := ch.Metadata.Dependencies; reqs != nil {
    78  		if err := CheckDependencies(ch, reqs); err != nil {
    79  			return "", err
    80  		}
    81  	}
    82  
    83  	var dest string
    84  	if p.Destination == "." {
    85  		// Save to the current working directory.
    86  		dest, err = os.Getwd()
    87  		if err != nil {
    88  			return "", err
    89  		}
    90  	} else {
    91  		// Otherwise save to set destination
    92  		dest = p.Destination
    93  	}
    94  
    95  	name, err := chartutil.Save(ch, dest)
    96  	if err != nil {
    97  		return "", errors.Wrap(err, "failed to save")
    98  	}
    99  
   100  	if p.Sign {
   101  		err = p.Clearsign(name)
   102  	}
   103  
   104  	return name, err
   105  }
   106  
   107  // validateVersion Verify that version is a Version, and error out if it is not.
   108  func validateVersion(ver string) error {
   109  	if _, err := semver.NewVersion(ver); err != nil {
   110  		return err
   111  	}
   112  	return nil
   113  }
   114  
   115  // Clearsign signs a chart
   116  func (p *Package) Clearsign(filename string) error {
   117  	// Load keyring
   118  	signer, err := provenance.NewFromKeyring(p.Keyring, p.Key)
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	passphraseFetcher := promptUser
   124  	if p.PassphraseFile != "" {
   125  		passphraseFetcher, err = passphraseFileFetcher(p.PassphraseFile, os.Stdin)
   126  		if err != nil {
   127  			return err
   128  		}
   129  	}
   130  
   131  	if err := signer.DecryptKey(passphraseFetcher); err != nil {
   132  		return err
   133  	}
   134  
   135  	sig, err := signer.ClearSign(filename)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	return ioutil.WriteFile(filename+".prov", []byte(sig), 0644)
   141  }
   142  
   143  // promptUser implements provenance.PassphraseFetcher
   144  func promptUser(name string) ([]byte, error) {
   145  	fmt.Printf("Password for key %q >  ", name)
   146  	// syscall.Stdin is not an int in all environments and needs to be coerced
   147  	// into one there (e.g., Windows)
   148  	pw, err := term.ReadPassword(int(syscall.Stdin))
   149  	fmt.Println()
   150  	return pw, err
   151  }
   152  
   153  func passphraseFileFetcher(passphraseFile string, stdin *os.File) (provenance.PassphraseFetcher, error) {
   154  	file, err := openPassphraseFile(passphraseFile, stdin)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	defer file.Close()
   159  
   160  	reader := bufio.NewReader(file)
   161  	passphrase, _, err := reader.ReadLine()
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	return func(name string) ([]byte, error) {
   166  		return passphrase, nil
   167  	}, nil
   168  }
   169  
   170  func openPassphraseFile(passphraseFile string, stdin *os.File) (*os.File, error) {
   171  	if passphraseFile == "-" {
   172  		stat, err := stdin.Stat()
   173  		if err != nil {
   174  			return nil, err
   175  		}
   176  		if (stat.Mode() & os.ModeNamedPipe) == 0 {
   177  			return nil, errors.New("specified reading passphrase from stdin, without input on stdin")
   178  		}
   179  		return stdin, nil
   180  	}
   181  	return os.Open(passphraseFile)
   182  }