integrate `helm chart *` commands into helm

Signed-off-by: Matthew Fisher <matt.fisher@microsoft.com>
pull/5635/head
Matthew Fisher 7 years ago
commit d0e046549f
No known key found for this signature in database
GPG Key ID: 92AA783CBAAE8E3B

@ -41,10 +41,7 @@ func newChartCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
cmd.AddCommand(
newChartListCmd(cfg, out),
newChartExportCmd(cfg, out),
newChartPullCmd(cfg, out),
newChartPushCmd(cfg, out),
newChartRemoveCmd(cfg, out),
newChartSaveCmd(cfg, out),
)
return cmd
}

@ -1,45 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"io"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
)
const chartPullDesc = `
Download a chart from a remote registry.
This will store the chart in the local registry cache to be used later.
`
func newChartPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{
Use: "pull [ref]",
Short: "pull a chart from remote",
Long: chartPullDesc,
Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ref := args[0]
return action.NewChartPull(cfg).Run(out, ref)
},
}
}

@ -1,47 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"io"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
)
const chartPushDesc = `
Upload a chart to a remote registry.
Note: the ref must already exist in the local registry cache.
Must first run "helm chart save" or "helm chart pull".
`
func newChartPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{
Use: "push [ref]",
Short: "push a chart to remote",
Long: chartPushDesc,
Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ref := args[0]
return action.NewChartPush(cfg).Run(out, ref)
},
}
}

@ -1,47 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"io"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
)
const chartSaveDesc = `
Store a copy of chart in local registry cache.
Note: modifying the chart after this operation will
not change the item as it exists in the cache.
`
func newChartSaveCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{
Use: "save [path] [ref]",
Short: "save a chart directory",
Long: chartSaveDesc,
Args: require.MinimumNArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
path := args[0]
ref := args[1]
return action.NewChartSave(cfg).Run(out, path, ref)
},
}
}

@ -81,7 +81,7 @@ func (o *createOptions) run(out io.Writer) error {
Type: "application",
Version: "0.1.0",
AppVersion: "1.0",
APIVersion: chart.APIVersionv1,
APIVersion: chart.APIVersionV1,
}
if o.starter != "" {

@ -55,7 +55,7 @@ func TestCreateCmd(t *testing.T) {
if c.Name() != cname {
t.Errorf("Expected %q name, got %q", cname, c.Name())
}
if c.Metadata.APIVersion != chart.APIVersionv1 {
if c.Metadata.APIVersion != chart.APIVersionV1 {
t.Errorf("Wrong API version: %q", c.Metadata.APIVersion)
}
}
@ -73,7 +73,11 @@ func TestCreateStarterCmd(t *testing.T) {
// Create a starter.
starterchart := hh.Starters()
os.Mkdir(starterchart, 0755)
if dest, err := chartutil.Create(&chart.Metadata{Name: "starterchart"}, starterchart); err != nil {
if dest, err := chartutil.Create(&chart.Metadata{
APIVersion: chart.APIVersionV1,
Name: "starterchart",
Version: "0.1.0",
}, starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err)
} else {
t.Logf("Created %s", dest)
@ -106,7 +110,7 @@ func TestCreateStarterCmd(t *testing.T) {
if c.Name() != cname {
t.Errorf("Expected %q name, got %q", cname, c.Name())
}
if c.Metadata.APIVersion != chart.APIVersionv1 {
if c.Metadata.APIVersion != chart.APIVersionV1 {
t.Errorf("Wrong API version: %q", c.Metadata.APIVersion)
}

@ -82,7 +82,7 @@ the contents of a chart.
This will produce an error if the chart cannot be loaded.
`
func newDependencyCmd(out io.Writer) *cobra.Command {
func newDependencyCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "dependency update|build|list",
Aliases: []string{"dep", "dependencies"},
@ -92,8 +92,8 @@ func newDependencyCmd(out io.Writer) *cobra.Command {
}
cmd.AddCommand(newDependencyListCmd(out))
cmd.AddCommand(newDependencyUpdateCmd(out))
cmd.AddCommand(newDependencyBuildCmd(out))
cmd.AddCommand(newDependencyUpdateCmd(cfg, out))
cmd.AddCommand(newDependencyBuildCmd(cfg, out))
return cmd
}

@ -26,7 +26,6 @@ import (
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/getter"
)
const dependencyBuildDesc = `
@ -40,7 +39,7 @@ If no lock file is found, 'helm dependency build' will mirror the behavior
of 'helm dependency update'.
`
func newDependencyBuildCmd(out io.Writer) *cobra.Command {
func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewDependency()
cmd := &cobra.Command{
@ -56,12 +55,12 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
man := &downloader.Manager{
Out: out,
ChartPath: chartpath,
HelmHome: settings.Home,
Keyring: client.Keyring,
Getters: getter.All(settings),
Client: cfg.RegistryClient,
}
if client.Verify {
man.Verify = downloader.VerifyIfPossible
// TODO(bacongobbler): plug into pkg/repo or oras for signing during a pull
//
// see comment in pkg/repo/client.go#PullChart
}
if settings.Debug {
man.Debug = true

@ -24,7 +24,6 @@ import (
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/getter"
)
const dependencyUpDesc = `
@ -43,7 +42,7 @@ in the Chart.yaml file, but (b) at the wrong version.
`
// newDependencyUpdateCmd creates a new dependency update command.
func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewDependency()
cmd := &cobra.Command{
@ -58,15 +57,14 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
chartpath = filepath.Clean(args[0])
}
man := &downloader.Manager{
Out: out,
ChartPath: chartpath,
HelmHome: settings.Home,
Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings),
Out: out,
ChartPath: chartpath,
Client: cfg.RegistryClient,
}
if client.Verify {
man.Verify = downloader.VerifyAlways
// TODO(bacongobbler): plug into pkg/repo or oras for signing during a pull
//
// see comment in pkg/repo/client.go#PullChart
}
if settings.Debug {
man.Debug = true

@ -205,8 +205,9 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
// The baseURL can be used to point to a particular repository server.
func createTestingMetadata(name, baseURL string) *chart.Metadata {
return &chart.Metadata{
Name: name,
Version: "1.2.3",
APIVersion: chart.APIVersionV1,
Name: name,
Version: "1.2.3",
Dependencies: []*chart.Dependency{
{Name: "reqtest", Version: "0.1.0", Repository: baseURL},
{Name: "compressedchart", Version: "0.1.0", Repository: baseURL},

@ -41,7 +41,6 @@ func newHomeCmd(out io.Writer) *cobra.Command {
fmt.Fprintln(out, h)
if settings.Debug {
fmt.Fprintf(out, "Repository: %s\n", h.Repository())
fmt.Fprintf(out, "RepositoryFile: %s\n", h.RepositoryFile())
fmt.Fprintf(out, "Cache: %s\n", h.Cache())
fmt.Fprintf(out, "Stable CacheIndex: %s\n", h.CacheIndex("stable"))
fmt.Fprintf(out, "Starters: %s\n", h.Starters())

@ -28,26 +28,17 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/plugin"
"helm.sh/helm/pkg/plugin/installer"
"helm.sh/helm/pkg/repo"
)
const initDesc = `
This command sets up local configuration in $HELM_HOME (default ~/.helm/).
`
const (
stableRepository = "stable"
defaultStableRepositoryURL = "https://kubernetes-charts.storage.googleapis.com"
)
type initOptions struct {
skipRefresh bool // --skip-refresh
stableRepositoryURL string // --stable-repo-url
pluginsFilename string // --plugins
pluginsFilename string // --plugins
home helmpath.Home
}
@ -76,8 +67,6 @@ func newInitCmd(out io.Writer) *cobra.Command {
}
f := cmd.Flags()
f.BoolVar(&o.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache")
f.StringVar(&o.stableRepositoryURL, "stable-repo-url", defaultStableRepositoryURL, "URL for stable repository")
f.StringVar(&o.pluginsFilename, "plugins", "", "a YAML file specifying plugins to install")
return cmd
@ -88,12 +77,6 @@ func (o *initOptions) run(out io.Writer) error {
if err := ensureDirectories(o.home, out); err != nil {
return err
}
if err := ensureDefaultRepos(o.home, out, o.skipRefresh, o.stableRepositoryURL); err != nil {
return err
}
if err := ensureRepoFileFormat(o.home.RepositoryFile(), out); err != nil {
return err
}
if o.pluginsFilename != "" {
if err := ensurePluginsInstalled(o.pluginsFilename, out); err != nil {
return err
@ -130,61 +113,6 @@ func ensureDirectories(home helmpath.Home, out io.Writer) error {
return nil
}
func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool, url string) error {
repoFile := home.RepositoryFile()
if fi, err := os.Stat(repoFile); err != nil {
fmt.Fprintf(out, "Creating %s \n", repoFile)
f := repo.NewFile()
sr, err := initRepo(url, home.CacheIndex(stableRepository), out, skipRefresh, home)
if err != nil {
return err
}
f.Add(sr)
if err := f.WriteFile(repoFile, 0644); err != nil {
return err
}
} else if fi.IsDir() {
return errors.Errorf("%s must be a file, not a directory", repoFile)
}
return nil
}
func initRepo(url, cacheFile string, out io.Writer, skipRefresh bool, home helmpath.Home) (*repo.Entry, error) {
fmt.Fprintf(out, "Adding %s repo with URL: %s \n", stableRepository, url)
c := repo.Entry{
Name: stableRepository,
URL: url,
Cache: cacheFile,
}
r, err := repo.NewChartRepository(&c, getter.All(settings))
if err != nil {
return nil, err
}
if skipRefresh {
return &c, nil
}
// In this case, the cacheFile is always absolute. So passing empty string
// is safe.
if err := r.DownloadIndexFile(""); err != nil {
return nil, errors.Wrapf(err, "%s is not a valid chart repository or cannot be reached", url)
}
return &c, nil
}
func ensureRepoFileFormat(file string, out io.Writer) error {
r, err := repo.LoadFile(file)
if err == repo.ErrRepoOutOfDate {
fmt.Fprintln(out, "Updating repository file format...")
if err := r.WriteFile(file, 0644); err != nil {
return err
}
}
return nil
}
func ensurePluginsInstalled(pluginsFilename string, out io.Writer) error {
bytes, err := ioutil.ReadFile(pluginsFilename)
if err != nil {

@ -26,10 +26,7 @@ import (
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/getter"
)
const installDesc = `
@ -103,7 +100,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: installDesc,
Args: require.MinimumNArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
rel, err := runInstall(args, client, out)
rel, err := runInstall(args, client, cfg, out)
if err != nil {
return err
}
@ -126,7 +123,6 @@ func addInstallFlags(f *pflag.FlagSet, client *action.Install) {
f.BoolVarP(&client.GenerateName, "generate-name", "g", false, "generate the name (and omit the NAME parameter)")
f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release")
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart")
addValueOptionsFlags(f, &client.ValueOptions)
addChartPathOptionsFlags(f, &client.ChartPathOptions)
}
@ -149,7 +145,7 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
}
func runInstall(args []string, client *action.Install, out io.Writer) (*release.Release, error) {
func runInstall(args []string, client *action.Install, cfg *action.Configuration, out io.Writer) (*release.Release, error) {
debug("Original chart version: %q", client.Version)
if client.Version == "" && client.Devel {
debug("setting version to >0.0.0-0")
@ -162,23 +158,17 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release.
}
client.ReleaseName = name
cp, err := client.ChartPathOptions.LocateChart(chart, settings)
chartRequested, err := action.LocateChart(chart, client.ChartPathOptions, settings, cfg.RegistryClient)
if err != nil {
return nil, err
}
debug("CHART PATH: %s\n", cp)
debug("CHART: %s\n", chartRequested)
if err := client.ValueOptions.MergeValues(settings); err != nil {
return nil, err
}
// Check chart dependencies to make sure all are present in /charts
chartRequested, err := loader.Load(cp)
if err != nil {
return nil, err
}
validInstallableChart, err := chartutil.IsChartInstallable(chartRequested)
if !validInstallableChart {
return nil, err
@ -189,21 +179,7 @@ func runInstall(args []string, client *action.Install, out io.Writer) (*release.
// As of Helm 2.4.0, this is treated as a stopping condition:
// https://github.com/helm/helm/issues/2209
if err := action.CheckDependencies(chartRequested, req); err != nil {
if client.DependencyUpdate {
man := &downloader.Manager{
Out: out,
ChartPath: cp,
HelmHome: settings.Home,
Keyring: client.ChartPathOptions.Keyring,
SkipUpdate: false,
Getters: getter.All(settings),
}
if err := man.Update(); err != nil {
return nil, err
}
} else {
return nil, err
}
return nil, err
}
}

@ -113,7 +113,7 @@ func TestInstall(t *testing.T) {
},
// Install, chart with bad dependencies in Chart.yaml in /charts
{
name: "install chart with bad dependencies in Chart.yaml",
name: "install chart with bad dependencies in Chart.yaml",
cmd: "install badreq testdata/testcharts/chart-bad-requirements",
wantError: true,
},

@ -28,7 +28,6 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/getter"
)
const packageDesc = `
@ -42,8 +41,8 @@ Chart.yaml file, and (if found) build the current directory into a chart.
Versioned chart archives are used by Helm package repositories.
`
func newPackageCmd(out io.Writer) *cobra.Command {
client := action.NewPackage()
func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewPackage(cfg)
cmd := &cobra.Command{
Use: "package [CHART_PATH] [...]",
@ -75,9 +74,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
downloadManager := &downloader.Manager{
Out: ioutil.Discard,
ChartPath: path,
HelmHome: settings.Home,
Keyring: client.Keyring,
Getters: getter.All(settings),
Client: cfg.RegistryClient,
Debug: settings.Debug,
}
@ -85,11 +82,10 @@ func newPackageCmd(out io.Writer) *cobra.Command {
return err
}
}
p, err := client.Run(path)
if err != nil {
if err := client.Run(path); err != nil {
return err
}
fmt.Fprintf(out, "Successfully packaged chart and saved it to: %s\n", p)
fmt.Fprintln(out, "Successfully packaged")
}
return nil
},
@ -101,7 +97,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "location of a public keyring")
f.StringVar(&client.Version, "version", "", "set the version on the chart to this semver version")
f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version")
f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.")
f.StringVar(&client.Registry, "registry", "", "name of the registry where this package will be published")
f.BoolVarP(&client.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`)
addValueOptionsFlags(f, &client.ValueOptions)

@ -27,7 +27,7 @@ import (
)
const pullDesc = `
Retrieve a package from a package repository, and download it locally.
Retrieve a package from a registry and download it locally.
This is useful for fetching packages to inspect, modify, or repackage. It can
also be used to perform cryptographic verification of a chart without installing
@ -41,12 +41,12 @@ file, and MUST pass the verification process. Failure in any part of this will
result in an error, and the chart will not be saved locally.
`
func newPullCmd(out io.Writer) *cobra.Command {
client := action.NewPull()
func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewPull(cfg)
cmd := &cobra.Command{
Use: "pull [chart URL | repo/chartname] [...]",
Short: "download a chart from a repository and (optionally) unpack it in local directory",
Short: "download a chart from a registry and (optionally) unpack it in local directory",
Aliases: []string{"fetch"},
Long: pullDesc,
Args: require.MinimumNArgs(1),

@ -0,0 +1,58 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"io"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
)
const pushDesc = `
Upload a chart to a registry.
This command will take a distributable, compressed chart and upload it to a registry.
The chart must already exist in the local registry cache, which can be created with either "helm package" or "helm pull".
Some registries require you to be authenticated with "helm login" before uploading a chart.
`
func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return &cobra.Command{
Use: "push [...]",
Short: "upload a chart to a registry",
Long: pushDesc,
Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.Errorf("need at least one argument: the name of the chart to push")
}
for i := 0; i < len(args); i++ {
ref := args[i]
if err := action.NewChartPush(cfg).Run(out, ref); err != nil {
return err
}
}
return nil
},
}
}

@ -1,50 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"io"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
)
var repoHelm = `
This command consists of multiple subcommands to interact with chart repositories.
It can be used to add, remove, list, and index chart repositories.
Example usage:
$ helm repo add [NAME] [REPO_URL]
`
func newRepoCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "repo add|remove|list|index|update [ARGS]",
Short: "add, list, remove, update, and index chart repositories",
Long: repoHelm,
Args: require.NoArgs,
}
cmd.AddCommand(newRepoAddCmd(out))
cmd.AddCommand(newRepoListCmd(out))
cmd.AddCommand(newRepoRemoveCmd(out))
cmd.AddCommand(newRepoIndexCmd(out))
cmd.AddCommand(newRepoUpdateCmd(out))
return cmd
}

@ -1,114 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"io"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo"
)
type repoAddOptions struct {
name string
url string
username string
password string
home helmpath.Home
noupdate bool
certFile string
keyFile string
caFile string
}
func newRepoAddCmd(out io.Writer) *cobra.Command {
o := &repoAddOptions{}
cmd := &cobra.Command{
Use: "add [NAME] [URL]",
Short: "add a chart repository",
Args: require.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
o.name = args[0]
o.url = args[1]
o.home = settings.Home
return o.run(out)
},
}
f := cmd.Flags()
f.StringVar(&o.username, "username", "", "chart repository username")
f.StringVar(&o.password, "password", "", "chart repository password")
f.BoolVar(&o.noupdate, "no-update", false, "raise error if repo is already registered")
f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
return cmd
}
func (o *repoAddOptions) run(out io.Writer) error {
if err := addRepository(o.name, o.url, o.username, o.password, o.home, o.certFile, o.keyFile, o.caFile, o.noupdate); err != nil {
return err
}
fmt.Fprintf(out, "%q has been added to your repositories\n", o.name)
return nil
}
func addRepository(name, url, username, password string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error {
f, err := repo.LoadFile(home.RepositoryFile())
if err != nil {
return err
}
if noUpdate && f.Has(name) {
return errors.Errorf("repository name (%s) already exists, please specify a different name", name)
}
cif := home.CacheIndex(name)
c := repo.Entry{
Name: name,
Cache: cif,
URL: url,
Username: username,
Password: password,
CertFile: certFile,
KeyFile: keyFile,
CAFile: caFile,
}
r, err := repo.NewChartRepository(&c, getter.All(settings))
if err != nil {
return err
}
if err := r.DownloadIndexFile(home.Cache()); err != nil {
return errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url)
}
f.Update(&c)
return f.WriteFile(home.RepositoryFile(), 0644)
}

@ -1,89 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"os"
"testing"
"helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/repo/repotest"
)
func TestRepoAddCmd(t *testing.T) {
defer resetEnv()()
srv, hh, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
defer func() {
srv.Stop()
os.RemoveAll(hh.String())
}()
ensureTestHome(t, hh)
settings.Home = hh
tests := []cmdTestCase{{
name: "add a repository",
cmd: fmt.Sprintf("repo add test-name %s --home '%s'", srv.URL(), hh),
golden: "output/repo-add.txt",
}}
runTestCmd(t, tests)
}
func TestRepoAdd(t *testing.T) {
defer resetEnv()()
ts, hh, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
defer func() {
ts.Stop()
os.RemoveAll(hh.String())
}()
ensureTestHome(t, hh)
settings.Home = hh
const testRepoName = "test-name"
if err := addRepository(testRepoName, ts.URL(), "", "", hh, "", "", "", true); err != nil {
t.Error(err)
}
f, err := repo.LoadFile(hh.RepositoryFile())
if err != nil {
t.Error(err)
}
if !f.Has(testRepoName) {
t.Errorf("%s was not successfully inserted into %s", testRepoName, hh.RepositoryFile())
}
if err := addRepository(testRepoName, ts.URL(), "", "", hh, "", "", "", false); err != nil {
t.Errorf("Repository was not updated: %s", err)
}
if err := addRepository(testRepoName, ts.URL(), "", "", hh, "", "", "", false); err != nil {
t.Errorf("Duplicate repository name was added")
}
}

@ -1,101 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"io"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/repo"
)
const repoIndexDesc = `
Read the current directory and generate an index file based on the charts found.
This tool is used for creating an 'index.yaml' file for a chart repository. To
set an absolute URL to the charts, use '--url' flag.
To merge the generated index with an existing index file, use the '--merge'
flag. In this case, the charts found in the current directory will be merged
into the existing index, with local charts taking priority over existing charts.
`
type repoIndexOptions struct {
dir string
url string
merge string
}
func newRepoIndexCmd(out io.Writer) *cobra.Command {
o := &repoIndexOptions{}
cmd := &cobra.Command{
Use: "index [DIR]",
Short: "generate an index file given a directory containing packaged charts",
Long: repoIndexDesc,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
o.dir = args[0]
return o.run(out)
},
}
f := cmd.Flags()
f.StringVar(&o.url, "url", "", "url of chart repository")
f.StringVar(&o.merge, "merge", "", "merge the generated index into the given index")
return cmd
}
func (i *repoIndexOptions) run(out io.Writer) error {
path, err := filepath.Abs(i.dir)
if err != nil {
return err
}
return index(path, i.url, i.merge)
}
func index(dir, url, mergeTo string) error {
out := filepath.Join(dir, "index.yaml")
i, err := repo.IndexDirectory(dir, url)
if err != nil {
return err
}
if mergeTo != "" {
// if index.yaml is missing then create an empty one to merge into
var i2 *repo.IndexFile
if _, err := os.Stat(mergeTo); os.IsNotExist(err) {
i2 = repo.NewIndexFile()
i2.WriteFile(mergeTo, 0755)
} else {
i2, err = repo.LoadIndexFile(mergeTo)
if err != nil {
return errors.Wrap(err, "merge failed")
}
}
i.Merge(i2)
}
i.SortEntries()
return i.WriteFile(out, 0755)
}

@ -1,166 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"bytes"
"io"
"os"
"path/filepath"
"testing"
"helm.sh/helm/pkg/repo"
)
func TestRepoIndexCmd(t *testing.T) {
dir := testTempDir(t)
comp := filepath.Join(dir, "compressedchart-0.1.0.tgz")
if err := linkOrCopy("testdata/testcharts/compressedchart-0.1.0.tgz", comp); err != nil {
t.Fatal(err)
}
comp2 := filepath.Join(dir, "compressedchart-0.2.0.tgz")
if err := linkOrCopy("testdata/testcharts/compressedchart-0.2.0.tgz", comp2); err != nil {
t.Fatal(err)
}
buf := bytes.NewBuffer(nil)
c := newRepoIndexCmd(buf)
if err := c.RunE(c, []string{dir}); err != nil {
t.Error(err)
}
destIndex := filepath.Join(dir, "index.yaml")
index, err := repo.LoadIndexFile(destIndex)
if err != nil {
t.Fatal(err)
}
if len(index.Entries) != 1 {
t.Errorf("expected 1 entry, got %d: %#v", len(index.Entries), index.Entries)
}
vs := index.Entries["compressedchart"]
if len(vs) != 2 {
t.Errorf("expected 2 versions, got %d: %#v", len(vs), vs)
}
expectedVersion := "0.2.0"
if vs[0].Version != expectedVersion {
t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version)
}
// Test with `--merge`
// Remove first two charts.
if err := os.Remove(comp); err != nil {
t.Fatal(err)
}
if err := os.Remove(comp2); err != nil {
t.Fatal(err)
}
// Add a new chart and a new version of an existing chart
if err := linkOrCopy("testdata/testcharts/reqtest-0.1.0.tgz", filepath.Join(dir, "reqtest-0.1.0.tgz")); err != nil {
t.Fatal(err)
}
if err := linkOrCopy("testdata/testcharts/compressedchart-0.3.0.tgz", filepath.Join(dir, "compressedchart-0.3.0.tgz")); err != nil {
t.Fatal(err)
}
c.ParseFlags([]string{"--merge", destIndex})
if err := c.RunE(c, []string{dir}); err != nil {
t.Error(err)
}
index, err = repo.LoadIndexFile(destIndex)
if err != nil {
t.Fatal(err)
}
if len(index.Entries) != 2 {
t.Errorf("expected 2 entries, got %d: %#v", len(index.Entries), index.Entries)
}
vs = index.Entries["compressedchart"]
if len(vs) != 3 {
t.Errorf("expected 3 versions, got %d: %#v", len(vs), vs)
}
expectedVersion = "0.3.0"
if vs[0].Version != expectedVersion {
t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version)
}
// test that index.yaml gets generated on merge even when it doesn't exist
if err := os.Remove(destIndex); err != nil {
t.Fatal(err)
}
c.ParseFlags([]string{"--merge", destIndex})
if err := c.RunE(c, []string{dir}); err != nil {
t.Error(err)
}
_, err = repo.LoadIndexFile(destIndex)
if err != nil {
t.Fatal(err)
}
// verify it didn't create an empty index.yaml and the merged happened
if len(index.Entries) != 2 {
t.Errorf("expected 2 entries, got %d: %#v", len(index.Entries), index.Entries)
}
vs = index.Entries["compressedchart"]
if len(vs) != 3 {
t.Errorf("expected 3 versions, got %d: %#v", len(vs), vs)
}
expectedVersion = "0.3.0"
if vs[0].Version != expectedVersion {
t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version)
}
}
func linkOrCopy(old, new string) error {
if err := os.Link(old, new); err != nil {
return copyFile(old, new)
}
return nil
}
func copyFile(dst, src string) error {
i, err := os.Open(dst)
if err != nil {
return err
}
defer i.Close()
o, err := os.Create(src)
if err != nil {
return err
}
defer o.Close()
_, err = io.Copy(o, i)
return err
}

@ -1,67 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"io"
"github.com/gosuri/uitable"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo"
)
type repoListOptions struct {
home helmpath.Home
}
func newRepoListCmd(out io.Writer) *cobra.Command {
o := &repoListOptions{}
cmd := &cobra.Command{
Use: "list",
Short: "list chart repositories",
Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
o.home = settings.Home
return o.run(out)
},
}
return cmd
}
func (o *repoListOptions) run(out io.Writer) error {
f, err := repo.LoadFile(o.home.RepositoryFile())
if err != nil {
return err
}
if len(f.Repositories) == 0 {
return errors.New("no repositories to show")
}
table := uitable.New()
table.AddRow("NAME", "URL")
for _, re := range f.Repositories {
table.AddRow(re.Name, re.URL)
}
fmt.Fprintln(out, table)
return nil
}

@ -1,91 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"io"
"os"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo"
)
type repoRemoveOptions struct {
name string
home helmpath.Home
}
func newRepoRemoveCmd(out io.Writer) *cobra.Command {
o := &repoRemoveOptions{}
cmd := &cobra.Command{
Use: "remove [NAME]",
Aliases: []string{"rm"},
Short: "remove a chart repository",
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
o.name = args[0]
o.home = settings.Home
return o.run(out)
},
}
return cmd
}
func (r *repoRemoveOptions) run(out io.Writer) error {
return removeRepoLine(out, r.name, r.home)
}
func removeRepoLine(out io.Writer, name string, home helmpath.Home) error {
repoFile := home.RepositoryFile()
r, err := repo.LoadFile(repoFile)
if err != nil {
return err
}
if !r.Remove(name) {
return errors.Errorf("no repo named %q found", name)
}
if err := r.WriteFile(repoFile, 0644); err != nil {
return err
}
if err := removeRepoCache(name, home); err != nil {
return err
}
fmt.Fprintf(out, "%q has been removed from your repositories\n", name)
return nil
}
func removeRepoCache(name string, home helmpath.Home) error {
if _, err := os.Stat(home.CacheIndex(name)); err == nil {
err = os.Remove(home.CacheIndex(name))
if err != nil {
return err
}
}
return nil
}

@ -1,77 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"bytes"
"os"
"strings"
"testing"
"helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/repo/repotest"
)
func TestRepoRemove(t *testing.T) {
defer resetEnv()()
ts, hh, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
defer func() {
ts.Stop()
os.RemoveAll(hh.String())
}()
ensureTestHome(t, hh)
settings.Home = hh
const testRepoName = "test-name"
b := bytes.NewBuffer(nil)
if err := removeRepoLine(b, testRepoName, hh); err == nil {
t.Errorf("Expected error removing %s, but did not get one.", testRepoName)
}
if err := addRepository(testRepoName, ts.URL(), "", "", hh, "", "", "", true); err != nil {
t.Error(err)
}
mf, _ := os.Create(hh.CacheIndex(testRepoName))
mf.Close()
b.Reset()
if err := removeRepoLine(b, testRepoName, hh); err != nil {
t.Errorf("Error removing %s from repositories", testRepoName)
}
if !strings.Contains(b.String(), "has been removed") {
t.Errorf("Unexpected output: %s", b.String())
}
if _, err := os.Stat(hh.CacheIndex(testRepoName)); err == nil {
t.Errorf("Error cache file was not removed for repository %s", testRepoName)
}
f, err := repo.LoadFile(hh.RepositoryFile())
if err != nil {
t.Error(err)
}
if f.Has(testRepoName) {
t.Errorf("%s was not successfully removed from repositories list", testRepoName)
}
}

@ -1,103 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"io"
"sync"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo"
)
const updateDesc = `
Update gets the latest information about charts from the respective chart repositories.
Information is cached locally, where it is used by commands like 'helm search'.
'helm update' is the deprecated form of 'helm repo update'. It will be removed in
future releases.
`
var errNoRepositories = errors.New("no repositories found. You must add one before updating")
type repoUpdateOptions struct {
update func([]*repo.ChartRepository, io.Writer, helmpath.Home)
home helmpath.Home
}
func newRepoUpdateCmd(out io.Writer) *cobra.Command {
o := &repoUpdateOptions{update: updateCharts}
cmd := &cobra.Command{
Use: "update",
Aliases: []string{"up"},
Short: "update information of available charts locally from chart repositories",
Long: updateDesc,
Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
o.home = settings.Home
return o.run(out)
},
}
return cmd
}
func (o *repoUpdateOptions) run(out io.Writer) error {
f, err := repo.LoadFile(o.home.RepositoryFile())
if err != nil {
return err
}
if len(f.Repositories) == 0 {
return errNoRepositories
}
var repos []*repo.ChartRepository
for _, cfg := range f.Repositories {
r, err := repo.NewChartRepository(cfg, getter.All(settings))
if err != nil {
return err
}
repos = append(repos, r)
}
o.update(repos, out, o.home)
return nil
}
func updateCharts(repos []*repo.ChartRepository, out io.Writer, home helmpath.Home) {
fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...")
var wg sync.WaitGroup
for _, re := range repos {
wg.Add(1)
go func(re *repo.ChartRepository) {
defer wg.Done()
if err := re.DownloadIndexFile(home.Cache()); err != nil {
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err)
} else {
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name)
}
}(re)
}
wg.Wait()
fmt.Fprintln(out, "Update Complete. ⎈ Happy Helming!⎈ ")
}

@ -1,93 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"bytes"
"fmt"
"io"
"os"
"strings"
"testing"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/repo/repotest"
)
func TestUpdateCmd(t *testing.T) {
defer resetEnv()()
hh := testHelmHome(t)
settings.Home = hh
out := bytes.NewBuffer(nil)
// Instead of using the HTTP updater, we provide our own for this test.
// The TestUpdateCharts test verifies the HTTP behavior independently.
updater := func(repos []*repo.ChartRepository, out io.Writer, hh helmpath.Home) {
for _, re := range repos {
fmt.Fprintln(out, re.Config.Name)
}
}
o := &repoUpdateOptions{
update: updater,
home: hh,
}
if err := o.run(out); err != nil {
t.Fatal(err)
}
if got := out.String(); !strings.Contains(got, "charts") {
t.Errorf("Expected 'charts' got %q", got)
}
}
func TestUpdateCharts(t *testing.T) {
defer resetEnv()()
ts, hh, err := repotest.NewTempServer("testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
defer func() {
ts.Stop()
os.RemoveAll(hh.String())
}()
ensureTestHome(t, hh)
settings.Home = hh
r, err := repo.NewChartRepository(&repo.Entry{
Name: "charts",
URL: ts.URL(),
Cache: hh.CacheIndex("charts"),
}, getter.All(settings))
if err != nil {
t.Error(err)
}
b := bytes.NewBuffer(nil)
updateCharts([]*repo.ChartRepository{r}, b, hh)
got := b.String()
if strings.Contains(got, "Unable to get an update") {
t.Errorf("Failed to get a repo: %q", got)
}
if !strings.Contains(got, "Update Complete.") {
t.Error("Update was not successful")
}
}

@ -19,12 +19,11 @@ package main // import "helm.sh/helm/cmd/helm"
import (
"io"
"github.com/containerd/containerd/remotes/docker"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/registry"
"helm.sh/helm/pkg/repo"
)
var globalUsage = `The Kubernetes package manager
@ -66,26 +65,22 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
// set defaults from environment
settings.Init(flags)
// Add the registry client based on settings
// Add the repo client based on settings
// TODO: Move this elsewhere (first, settings.Init() must move)
actionConfig.RegistryClient = registry.NewClient(&registry.ClientOptions{
Out: out,
Resolver: registry.Resolver{
Resolver: docker.NewResolver(docker.ResolverOptions{}),
},
CacheRootDir: settings.Home.Registry(),
actionConfig.RegistryClient = repo.NewClient(&repo.ClientOptions{
Out: out,
CacheRootDir: settings.Home.Repository(),
})
cmd.AddCommand(
// chart commands
newCreateCmd(out),
newDependencyCmd(out),
newPullCmd(out),
newShowCmd(out),
newDependencyCmd(actionConfig, out),
newPullCmd(actionConfig, out),
newPushCmd(actionConfig, out),
newShowCmd(actionConfig, out),
newLintCmd(out),
newPackageCmd(out),
newRepoCmd(out),
newSearchCmd(out),
newPackageCmd(actionConfig, out),
newVerifyCmd(out),
newChartCmd(actionConfig, out),
@ -104,7 +99,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
newHomeCmd(out),
newInitCmd(out),
newPluginCmd(out),
newTemplateCmd(out),
newTemplateCmd(actionConfig, out),
newVersionCmd(out),
// Hidden documentation generator command: 'helm docs'

@ -1,163 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"io"
"strings"
"github.com/Masterminds/semver"
"github.com/gosuri/uitable"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/cmd/helm/search"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/repo"
)
const searchDesc = `
Search reads through all of the repositories configured on the system, and
looks for matches.
Repositories are managed with 'helm repo' commands.
`
// searchMaxScore suggests that any score higher than this is not considered a match.
const searchMaxScore = 25
type searchOptions struct {
helmhome helmpath.Home
versions bool
regexp bool
version string
}
func newSearchCmd(out io.Writer) *cobra.Command {
o := &searchOptions{}
cmd := &cobra.Command{
Use: "search [keyword]",
Short: "search for a keyword in charts",
Long: searchDesc,
RunE: func(cmd *cobra.Command, args []string) error {
o.helmhome = settings.Home
return o.run(out, args)
},
}
f := cmd.Flags()
f.BoolVarP(&o.regexp, "regexp", "r", false, "use regular expressions for searching")
f.BoolVarP(&o.versions, "versions", "l", false, "show the long listing, with each version of each chart on its own line")
f.StringVarP(&o.version, "version", "v", "", "search using semantic versioning constraints")
return cmd
}
func (o *searchOptions) run(out io.Writer, args []string) error {
index, err := o.buildIndex(out)
if err != nil {
return err
}
var res []*search.Result
if len(args) == 0 {
res = index.All()
} else {
q := strings.Join(args, " ")
res, err = index.Search(q, searchMaxScore, o.regexp)
if err != nil {
return err
}
}
search.SortScore(res)
data, err := o.applyConstraint(res)
if err != nil {
return err
}
fmt.Fprintln(out, o.formatSearchResults(data))
return nil
}
func (o *searchOptions) applyConstraint(res []*search.Result) ([]*search.Result, error) {
if len(o.version) == 0 {
return res, nil
}
constraint, err := semver.NewConstraint(o.version)
if err != nil {
return res, errors.Wrap(err, "an invalid version/constraint format")
}
data := res[:0]
foundNames := map[string]bool{}
for _, r := range res {
if _, found := foundNames[r.Name]; found {
continue
}
v, err := semver.NewVersion(r.Chart.Version)
if err != nil || constraint.Check(v) {
data = append(data, r)
if !o.versions {
foundNames[r.Name] = true // If user hasn't requested all versions, only show the latest that matches
}
}
}
return data, nil
}
func (o *searchOptions) formatSearchResults(res []*search.Result) string {
if len(res) == 0 {
return "No results found"
}
table := uitable.New()
table.MaxColWidth = 50
table.AddRow("NAME", "CHART VERSION", "APP VERSION", "DESCRIPTION")
for _, r := range res {
table.AddRow(r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description)
}
return table.String()
}
func (o *searchOptions) buildIndex(out io.Writer) (*search.Index, error) {
// Load the repositories.yaml
rf, err := repo.LoadFile(o.helmhome.RepositoryFile())
if err != nil {
return nil, err
}
i := search.NewIndex()
for _, re := range rf.Repositories {
n := re.Name
f := o.helmhome.CacheIndex(n)
ind, err := repo.LoadIndexFile(f)
if err != nil {
// TODO should print to stderr
fmt.Fprintf(out, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.", n)
continue
}
i.AddRepo(n, ind, o.versions || len(o.version) > 0)
}
return i, nil
}

@ -1,237 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*Package search provides client-side repository searching.
This supports building an in-memory search index based on the contents of
multiple repositories, and then using string matching or regular expressions
to find matches.
*/
package search
import (
"path"
"regexp"
"sort"
"strings"
"github.com/Masterminds/semver"
"github.com/pkg/errors"
"helm.sh/helm/pkg/repo"
)
// Result is a search result.
//
// Score indicates how close it is to match. The higher the score, the longer
// the distance.
type Result struct {
Name string
Score int
Chart *repo.ChartVersion
}
// Index is a searchable index of chart information.
type Index struct {
lines map[string]string
charts map[string]*repo.ChartVersion
}
const sep = "\v"
// NewIndex creats a new Index.
func NewIndex() *Index {
return &Index{lines: map[string]string{}, charts: map[string]*repo.ChartVersion{}}
}
// verSep is a separator for version fields in map keys.
const verSep = "$$"
// AddRepo adds a repository index to the search index.
func (i *Index) AddRepo(rname string, ind *repo.IndexFile, all bool) {
ind.SortEntries()
for name, ref := range ind.Entries {
if len(ref) == 0 {
// Skip chart names that have zero releases.
continue
}
// By convention, an index file is supposed to have the newest at the
// 0 slot, so our best bet is to grab the 0 entry and build the index
// entry off of that.
// Note: Do not use filePath.Join since on Windows it will return \
// which results in a repo name that cannot be understood.
fname := path.Join(rname, name)
if !all {
i.lines[fname] = indstr(rname, ref[0])
i.charts[fname] = ref[0]
continue
}
// If 'all' is set, then we go through all of the refs, and add them all
// to the index. This will generate a lot of near-duplicate entries.
for _, rr := range ref {
versionedName := fname + verSep + rr.Version
i.lines[versionedName] = indstr(rname, rr)
i.charts[versionedName] = rr
}
}
}
// All returns all charts in the index as if they were search results.
//
// Each will be given a score of 0.
func (i *Index) All() []*Result {
res := make([]*Result, len(i.charts))
j := 0
for name, ch := range i.charts {
parts := strings.Split(name, verSep)
res[j] = &Result{
Name: parts[0],
Chart: ch,
}
j++
}
return res
}
// Search searches an index for the given term.
//
// Threshold indicates the maximum score a term may have before being marked
// irrelevant. (Low score means higher relevance. Golf, not bowling.)
//
// If regexp is true, the term is treated as a regular expression. Otherwise,
// term is treated as a literal string.
func (i *Index) Search(term string, threshold int, regexp bool) ([]*Result, error) {
if regexp {
return i.SearchRegexp(term, threshold)
}
return i.SearchLiteral(term, threshold), nil
}
// calcScore calculates a score for a match.
func (i *Index) calcScore(index int, matchline string) int {
// This is currently tied to the fact that sep is a single char.
splits := []int{}
s := rune(sep[0])
for i, ch := range matchline {
if ch == s {
splits = append(splits, i)
}
}
for i, pos := range splits {
if index > pos {
continue
}
return i
}
return len(splits)
}
// SearchLiteral does a literal string search (no regexp).
func (i *Index) SearchLiteral(term string, threshold int) []*Result {
term = strings.ToLower(term)
buf := []*Result{}
for k, v := range i.lines {
lk := strings.ToLower(k)
lv := strings.ToLower(v)
res := strings.Index(lv, term)
if score := i.calcScore(res, lv); res != -1 && score < threshold {
parts := strings.Split(lk, verSep) // Remove version, if it is there.
buf = append(buf, &Result{Name: parts[0], Score: score, Chart: i.charts[k]})
}
}
return buf
}
// SearchRegexp searches using a regular expression.
func (i *Index) SearchRegexp(re string, threshold int) ([]*Result, error) {
matcher, err := regexp.Compile(re)
if err != nil {
return []*Result{}, err
}
buf := []*Result{}
for k, v := range i.lines {
ind := matcher.FindStringIndex(v)
if len(ind) == 0 {
continue
}
if score := i.calcScore(ind[0], v); ind[0] >= 0 && score < threshold {
parts := strings.Split(k, verSep) // Remove version, if it is there.
buf = append(buf, &Result{Name: parts[0], Score: score, Chart: i.charts[k]})
}
}
return buf, nil
}
// Chart returns the ChartVersion for a particular name.
func (i *Index) Chart(name string) (*repo.ChartVersion, error) {
c, ok := i.charts[name]
if !ok {
return nil, errors.New("no such chart")
}
return c, nil
}
// SortScore does an in-place sort of the results.
//
// Lowest scores are highest on the list. Matching scores are subsorted alphabetically.
func SortScore(r []*Result) {
sort.Sort(scoreSorter(r))
}
// scoreSorter sorts results by score, and subsorts by alpha Name.
type scoreSorter []*Result
// Len returns the length of this scoreSorter.
func (s scoreSorter) Len() int { return len(s) }
// Swap performs an in-place swap.
func (s scoreSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// Less compares a to b, and returns true if a is less than b.
func (s scoreSorter) Less(a, b int) bool {
first := s[a]
second := s[b]
if first.Score > second.Score {
return false
}
if first.Score < second.Score {
return true
}
if first.Name == second.Name {
v1, err := semver.NewVersion(first.Chart.Version)
if err != nil {
return true
}
v2, err := semver.NewVersion(second.Chart.Version)
if err != nil {
return true
}
// Sort so that the newest chart is higher than the oldest chart. This is
// the opposite of what you'd expect in a function called Less.
return v1.GreaterThan(v2)
}
return first.Name < second.Name
}
func indstr(name string, ref *repo.ChartVersion) string {
i := ref.Name + sep + name + "/" + ref.Name + sep +
ref.Description + sep + strings.Join(ref.Keywords, " ")
return i
}

@ -1,303 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package search
import (
"strings"
"testing"
"helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/repo"
)
func TestSortScore(t *testing.T) {
in := []*Result{
{Name: "bbb", Score: 0, Chart: &repo.ChartVersion{Metadata: &chart.Metadata{Version: "1.2.3"}}},
{Name: "aaa", Score: 5},
{Name: "abb", Score: 5},
{Name: "aab", Score: 0},
{Name: "bab", Score: 5},
{Name: "ver", Score: 5, Chart: &repo.ChartVersion{Metadata: &chart.Metadata{Version: "1.2.4"}}},
{Name: "ver", Score: 5, Chart: &repo.ChartVersion{Metadata: &chart.Metadata{Version: "1.2.3"}}},
}
expect := []string{"aab", "bbb", "aaa", "abb", "bab", "ver", "ver"}
expectScore := []int{0, 0, 5, 5, 5, 5, 5}
SortScore(in)
// Test Score
for i := 0; i < len(expectScore); i++ {
if expectScore[i] != in[i].Score {
t.Errorf("Sort error on index %d: expected %d, got %d", i, expectScore[i], in[i].Score)
}
}
// Test Name
for i := 0; i < len(expect); i++ {
if expect[i] != in[i].Name {
t.Errorf("Sort error: expected %s, got %s", expect[i], in[i].Name)
}
}
// Test version of last two items
if in[5].Chart.Version != "1.2.4" {
t.Errorf("Expected 1.2.4, got %s", in[5].Chart.Version)
}
if in[6].Chart.Version != "1.2.3" {
t.Error("Expected 1.2.3 to be last")
}
}
var indexfileEntries = map[string]repo.ChartVersions{
"niña": {
{
URLs: []string{"http://example.com/charts/nina-0.1.0.tgz"},
Metadata: &chart.Metadata{
Name: "niña",
Version: "0.1.0",
Description: "One boat",
},
},
},
"pinta": {
{
URLs: []string{"http://example.com/charts/pinta-0.1.0.tgz"},
Metadata: &chart.Metadata{
Name: "pinta",
Version: "0.1.0",
Description: "Two ship",
},
},
},
"santa-maria": {
{
URLs: []string{"http://example.com/charts/santa-maria-1.2.3.tgz"},
Metadata: &chart.Metadata{
Name: "santa-maria",
Version: "1.2.3",
Description: "Three boat",
},
},
{
URLs: []string{"http://example.com/charts/santa-maria-1.2.2-rc-1.tgz"},
Metadata: &chart.Metadata{
Name: "santa-maria",
Version: "1.2.2-RC-1",
Description: "Three boat",
},
},
},
}
func loadTestIndex(t *testing.T, all bool) *Index {
i := NewIndex()
i.AddRepo("testing", &repo.IndexFile{Entries: indexfileEntries}, all)
i.AddRepo("ztesting", &repo.IndexFile{Entries: map[string]repo.ChartVersions{
"pinta": {
{
URLs: []string{"http://example.com/charts/pinta-2.0.0.tgz"},
Metadata: &chart.Metadata{
Name: "pinta",
Version: "2.0.0",
Description: "Two ship, version two",
},
},
},
}}, all)
return i
}
func TestAll(t *testing.T) {
i := loadTestIndex(t, false)
all := i.All()
if len(all) != 4 {
t.Errorf("Expected 4 entries, got %d", len(all))
}
i = loadTestIndex(t, true)
all = i.All()
if len(all) != 5 {
t.Errorf("Expected 5 entries, got %d", len(all))
}
}
func TestAddRepo_Sort(t *testing.T) {
i := loadTestIndex(t, true)
sr, err := i.Search("TESTING/SANTA-MARIA", 100, false)
if err != nil {
t.Fatal(err)
}
SortScore(sr)
ch := sr[0]
expect := "1.2.3"
if ch.Chart.Version != expect {
t.Errorf("Expected %q, got %q", expect, ch.Chart.Version)
}
}
func TestSearchByName(t *testing.T) {
tests := []struct {
name string
query string
expect []*Result
regexp bool
fail bool
failMsg string
}{
{
name: "basic search for one result",
query: "santa-maria",
expect: []*Result{
{Name: "testing/santa-maria"},
},
},
{
name: "basic search for two results",
query: "pinta",
expect: []*Result{
{Name: "testing/pinta"},
{Name: "ztesting/pinta"},
},
},
{
name: "repo-specific search for one result",
query: "ztesting/pinta",
expect: []*Result{
{Name: "ztesting/pinta"},
},
},
{
name: "partial name search",
query: "santa",
expect: []*Result{
{Name: "testing/santa-maria"},
},
},
{
name: "description search, one result",
query: "Three",
expect: []*Result{
{Name: "testing/santa-maria"},
},
},
{
name: "description search, two results",
query: "two",
expect: []*Result{
{Name: "testing/pinta"},
{Name: "ztesting/pinta"},
},
},
{
name: "description upper search, two results",
query: "TWO",
expect: []*Result{
{Name: "testing/pinta"},
{Name: "ztesting/pinta"},
},
},
{
name: "nothing found",
query: "mayflower",
expect: []*Result{},
},
{
name: "regexp, one result",
query: "Th[ref]*",
expect: []*Result{
{Name: "testing/santa-maria"},
},
regexp: true,
},
{
name: "regexp, fail compile",
query: "th[",
expect: []*Result{},
regexp: true,
fail: true,
failMsg: "error parsing regexp:",
},
}
i := loadTestIndex(t, false)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
charts, err := i.Search(tt.query, 100, tt.regexp)
if err != nil {
if tt.fail {
if !strings.Contains(err.Error(), tt.failMsg) {
t.Fatalf("Unexpected error message: %s", err)
}
return
}
t.Fatalf("%s: %s", tt.name, err)
}
// Give us predictably ordered results.
SortScore(charts)
l := len(charts)
if l != len(tt.expect) {
t.Fatalf("Expected %d result, got %d", len(tt.expect), l)
}
// For empty result sets, just keep going.
if l == 0 {
return
}
for i, got := range charts {
ex := tt.expect[i]
if got.Name != ex.Name {
t.Errorf("[%d]: Expected name %q, got %q", i, ex.Name, got.Name)
}
}
})
}
}
func TestSearchByNameAll(t *testing.T) {
// Test with the All bit turned on.
i := loadTestIndex(t, true)
cs, err := i.Search("santa-maria", 100, false)
if err != nil {
t.Fatal(err)
}
if len(cs) != 2 {
t.Errorf("expected 2 charts, got %d", len(cs))
}
}
func TestCalcScore(t *testing.T) {
i := NewIndex()
fields := []string{"aaa", "bbb", "ccc", "ddd"}
matchline := strings.Join(fields, sep)
if r := i.calcScore(2, matchline); r != 0 {
t.Errorf("Expected 0, got %d", r)
}
if r := i.calcScore(5, matchline); r != 1 {
t.Errorf("Expected 1, got %d", r)
}
if r := i.calcScore(10, matchline); r != 2 {
t.Errorf("Expected 2, got %d", r)
}
if r := i.calcScore(14, matchline); r != 3 {
t.Errorf("Expected 3, got %d", r)
}
}

@ -1,72 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
)
func TestSearchCmd(t *testing.T) {
defer resetEnv()()
setHome := func(cmd string) string {
return cmd + " --home=testdata/helmhome"
}
tests := []cmdTestCase{{
name: "search for 'maria', expect one match",
cmd: setHome("search maria"),
golden: "output/search-single.txt",
}, {
name: "search for 'alpine', expect two matches",
cmd: setHome("search alpine"),
golden: "output/search-multiple.txt",
}, {
name: "search for 'alpine' with versions, expect three matches",
cmd: setHome("search alpine --versions"),
golden: "output/search-multiple-versions.txt",
}, {
name: "search for 'alpine' with version constraint, expect one match with version 0.1.0",
cmd: setHome("search alpine --version '>= 0.1, < 0.2'"),
golden: "output/search-constraint.txt",
}, {
name: "search for 'alpine' with version constraint, expect one match with version 0.1.0",
cmd: setHome("search alpine --versions --version '>= 0.1, < 0.2'"),
golden: "output/search-versions-constraint.txt",
}, {
name: "search for 'alpine' with version constraint, expect one match with version 0.2.0",
cmd: setHome("search alpine --version '>= 0.1'"),
golden: "output/search-constraint-single.txt",
}, {
name: "search for 'alpine' with version constraint and --versions, expect two matches",
cmd: setHome("search alpine --versions --version '>= 0.1'"),
golden: "output/search-multiple-versions-constraints.txt",
}, {
name: "search for 'syzygy', expect no matches",
cmd: setHome("search syzygy"),
golden: "output/search-not-found.txt",
}, {
name: "search for 'alp[a-z]+', expect two matches",
cmd: setHome("search alp[a-z]+ --regexp"),
golden: "output/search-regex.txt",
}, {
name: "search for 'alp[', expect failure to compile regexp",
cmd: setHome("search alp[ --regexp"),
wantError: true,
}}
runTestCmd(t, tests)
}

@ -48,7 +48,7 @@ This command inspects a chart (directory, file, or URL) and displays the content
of the README file
`
func newShowCmd(out io.Writer) *cobra.Command {
func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewShow(action.ShowAll)
showCommand := &cobra.Command{
@ -58,11 +58,11 @@ func newShowCmd(out io.Writer) *cobra.Command {
Long: showDesc,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
ch, err := action.LocateChart(args[0], client.ChartPathOptions, settings, cfg.RegistryClient)
if err != nil {
return err
}
output, err := client.Run(cp)
output, err := client.Run(ch)
if err != nil {
return err
}
@ -78,11 +78,11 @@ func newShowCmd(out io.Writer) *cobra.Command {
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowValues
cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
ch, err := action.LocateChart(args[0], client.ChartPathOptions, settings, cfg.RegistryClient)
if err != nil {
return err
}
output, err := client.Run(cp)
output, err := client.Run(ch)
if err != nil {
return err
}
@ -98,11 +98,11 @@ func newShowCmd(out io.Writer) *cobra.Command {
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowChart
cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
ch, err := action.LocateChart(args[0], client.ChartPathOptions, settings, cfg.RegistryClient)
if err != nil {
return err
}
output, err := client.Run(cp)
output, err := client.Run(ch)
if err != nil {
return err
}
@ -118,11 +118,11 @@ func newShowCmd(out io.Writer) *cobra.Command {
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowReadme
cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
ch, err := action.LocateChart(args[0], client.ChartPathOptions, settings, cfg.RegistryClient)
if err != nil {
return err
}
output, err := client.Run(cp)
output, err := client.Run(ch)
if err != nil {
return err
}

@ -45,7 +45,7 @@ To render just one template in a chart, use '-x':
$ helm template foo mychart -x templates/deployment.yaml
`
func newTemplateCmd(out io.Writer) *cobra.Command {
func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
customConfig := &action.Configuration{
// Add mock objects in here so it doesn't use Kube API server
Releases: storage.Init(driver.NewMemory()),
@ -67,7 +67,7 @@ func newTemplateCmd(out io.Writer) *cobra.Command {
client.DryRun = true
client.ReleaseName = "RELEASE-NAME"
client.Replace = true // Skip the name check
rel, err := runInstall(args, client, out)
rel, err := runInstall(args, client, cfg, out)
if err != nil {
return err
}

@ -1 +1 @@
Error: cannot load Chart.yaml: error converting YAML to JSON: yaml: line 5: did not find expected '-' indicator
Error: cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator

@ -1,3 +1,4 @@
apiVersion: v1
description: Deploy a basic Alpine Linux pod
home: https://helm.sh/helm
name: alpine

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes
name: chart-missing-deps
version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes
name: reqsubchart
version: 0.1.0

@ -1,7 +1,8 @@
apiVersion: v1
description: Deploy a basic Alpine Linux pod
home: https://helm.sh/helm
name: chart-bad-type
sources:
- https://github.com/helm/helm
- https://github.com/helm/helm
version: 0.1.0
type: foobar

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes
name: chart-missing-deps
version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes
name: reqsubchart
version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes
name: decompressedchart
version: 0.1.0

@ -1,6 +1,7 @@
apiVersion: v1
description: Empty testing chart
home: https://helm.sh/helm
name: empty
sources:
- https://github.com/helm/helm
- https://github.com/helm/helm
version: 0.1.0

@ -1,6 +1,7 @@
apiVersion: v1
description: Deploy a basic Alpine Linux pod
home: https://helm.sh/helm
name: alpine
sources:
- https://github.com/helm/helm
- https://github.com/helm/helm
version: 0.1.0

@ -1,6 +1,7 @@
apiVersion: v1
description: Deploy a basic Alpine Linux pod
home: https://helm.sh/helm
name: novals
sources:
- https://github.com/helm/helm
- https://github.com/helm/helm
version: 0.2.0

Binary file not shown.

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes
name: reqtest
version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes
name: reqsubchart
version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes
name: reqsubchart2
version: 0.2.0

Binary file not shown.

@ -1,20 +1,21 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
apiVersion: v1
description: A Helm chart for Kubernetes
name: signtest
version: 0.1.0
...
files:
signtest-0.1.0.tgz: sha256:dee72947753628425b82814516bdaa37aef49f25e8820dd2a6e15a33a007823b
signtest-0.1.0.tgz: sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55
-----BEGIN PGP SIGNATURE-----
wsBcBAEBCgAQBQJXomNHCRCEO7+YH8GHYgAALywIAG1Me852Fpn1GYu8Q1GCcw4g
l2k7vOFchdDwDhdSVbkh4YyvTaIO3iE2Jtk1rxw+RIJiUr0eLO/rnIJuxZS8WKki
DR1LI9J1VD4dxN3uDETtWDWq7ScoPsRY5mJvYZXC8whrWEt/H2kfqmoA9LloRPWp
flOE0iktA4UciZOblTj6nAk3iDyjh/4HYL4a6tT0LjjKI7OTw4YyHfjHad1ywVCz
9dMUc1rPgTnl+fnRiSPSrlZIWKOt1mcQ4fVrU3nwtRUwTId2k8FtygL0G6M+Y6t0
S6yaU7qfk9uTxkdkUF7Bf1X3ukxfe+cNBC32vf4m8LY4NkcYfSqK2fGtQsnVr6s=
=NyOM
wsBcBAEBCgAQBQJcoosfCRCEO7+YH8GHYgAA220IALAs8T8NPgkcLvHu+5109cAN
BOCNPSZDNsqLZW/2Dc9cKoBG7Jen4Qad+i5l9351kqn3D9Gm6eRfAWcjfggRobV/
9daZ19h0nl4O1muQNAkjvdgZt8MOP3+PB3I3/Tu2QCYjI579SLUmuXlcZR5BCFPR
PJy+e3QpV2PcdeU2KZLG4tjtlrq+3QC9ZHHEJLs+BVN9d46Dwo6CxJdHJrrrAkTw
M8MhA92vbiTTPRSCZI9x5qDAwJYhoq0oxLflpuL2tIlo3qVoCsaTSURwMESEHO32
XwYG7BaVDMELWhAorBAGBGBwWFbJ1677qQ2gd9CN0COiVhekWlFRcnn60800r84=
=k9Y9
-----END PGP SIGNATURE-----

@ -1,3 +1,4 @@
apiVersion: v1
description: A Helm chart for Kubernetes
name: signtest
version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
description: Deploy a basic Alpine Linux pod
home: https://helm.sh/helm
name: alpine

@ -25,7 +25,6 @@ import (
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/storage/driver"
)
@ -74,7 +73,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return err
}
chartPath, err := client.ChartPathOptions.LocateChart(args[1], settings)
ch, err := action.LocateChart(args[1], client.ChartPathOptions, settings, cfg.RegistryClient)
if err != nil {
return err
}
@ -96,16 +95,11 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.Devel = client.Devel
instClient.Namespace = client.Namespace
_, err := runInstall(args, instClient, out)
_, err := runInstall(args, instClient, cfg, out)
return err
}
}
// Check chart dependencies to make sure all are present in /charts
ch, err := loader.Load(chartPath)
if err != nil {
return err
}
if req := ch.Metadata.Dependencies; req != nil {
if err := action.CheckDependencies(ch, req); err != nil {
return err

@ -29,6 +29,7 @@ import (
func TestUpgradeCmd(t *testing.T) {
tmpChart := testTempDir(t)
cfile := &chart.Metadata{
APIVersion: chart.APIVersionV1,
Name: "testUpgradeChart",
Description: "A Helm chart for Kubernetes",
Version: "0.1.0",

@ -1,3 +1,4 @@
apiVersion: v1
name: alpine
description: Deploy a basic Alpine Linux pod
version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
name: nginx
description: A basic NGINX HTTP server
version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
name: alpine
description: Deploy a basic Alpine Linux pod
version: 0.1.0

@ -26,8 +26,8 @@ import (
"helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/kube"
"helm.sh/helm/pkg/registry"
"helm.sh/helm/pkg/release"
"helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/storage"
)
@ -70,8 +70,8 @@ type Configuration struct {
// KubeClient is a Kubernetes API client.
KubeClient kube.KubernetesClient
// RegistryClient is a client for working with registries
RegistryClient *registry.Client
// RegistryClient is a client for working with repositories
RegistryClient *repo.Client
Capabilities *chartutil.Capabilities

@ -20,8 +20,9 @@ import (
"fmt"
"io"
"github.com/containerd/containerd/reference"
"helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/registry"
)
// ChartExport performs a chart export operation.
@ -38,7 +39,7 @@ func NewChartExport(cfg *Configuration) *ChartExport {
// Run executes the chart export operation
func (a *ChartExport) Run(out io.Writer, ref string) error {
r, err := registry.ParseReference(ref)
r, err := reference.Parse(ref)
if err != nil {
return err
}

@ -19,7 +19,7 @@ package action
import (
"io"
"helm.sh/helm/pkg/registry"
"github.com/containerd/containerd/reference"
)
// ChartPull performs a chart pull operation.
@ -36,7 +36,7 @@ func NewChartPull(cfg *Configuration) *ChartPull {
// Run executes the chart pull operation
func (a *ChartPull) Run(out io.Writer, ref string) error {
r, err := registry.ParseReference(ref)
r, err := reference.Parse(ref)
if err != nil {
return err
}

@ -19,7 +19,7 @@ package action
import (
"io"
"helm.sh/helm/pkg/registry"
"github.com/containerd/containerd/reference"
)
// ChartPush performs a chart push operation.
@ -36,7 +36,7 @@ func NewChartPush(cfg *Configuration) *ChartPush {
// Run executes the chart push operation
func (a *ChartPush) Run(out io.Writer, ref string) error {
r, err := registry.ParseReference(ref)
r, err := reference.Parse(ref)
if err != nil {
return err
}

@ -19,7 +19,7 @@ package action
import (
"io"
"helm.sh/helm/pkg/registry"
"github.com/containerd/containerd/reference"
)
// ChartRemove performs a chart remove operation.
@ -36,7 +36,7 @@ func NewChartRemove(cfg *Configuration) *ChartRemove {
// Run executes the chart remove operation
func (a *ChartRemove) Run(out io.Writer, ref string) error {
r, err := registry.ParseReference(ref)
r, err := reference.Parse(ref)
if err != nil {
return err
}

@ -1,57 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"io"
"path/filepath"
"helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/registry"
)
// ChartSave performs a chart save operation.
type ChartSave struct {
cfg *Configuration
}
// NewChartSave creates a new ChartSave object with the given configuration.
func NewChartSave(cfg *Configuration) *ChartSave {
return &ChartSave{
cfg: cfg,
}
}
// Run executes the chart save operation
func (a *ChartSave) Run(out io.Writer, path, ref string) error {
path, err := filepath.Abs(path)
if err != nil {
return err
}
ch, err := loader.LoadDir(path)
if err != nil {
return err
}
r, err := registry.ParseReference(ref)
if err != nil {
return err
}
return a.cfg.RegistryClient.SaveChart(ch, r)
}

@ -140,9 +140,9 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency) s
func (d *Dependency) printDependencies(chartpath string, out io.Writer, reqs []*chart.Dependency) {
table := uitable.New()
table.MaxColWidth = 80
table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS")
table.AddRow("NAME", "VERSION", "STATUS")
for _, row := range reqs {
table.AddRow(row.Name, row.Version, row.Repository, d.dependencyStatus(chartpath, row))
table.AddRow(row.Name, row.Version, d.dependencyStatus(chartpath, row))
}
fmt.Fprintln(out, table)
}

@ -35,9 +35,9 @@ import (
"github.com/pkg/errors"
"helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chart/loader"
"helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/engine"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/hooks"
@ -521,78 +521,52 @@ OUTER:
return nil
}
// LocateChart looks for a chart directory in known places, and returns either the full path or an error.
// LocateChart looks for a chart directory in known places, and returns either the chart or an error.
//
// This does not ensure that the chart is well-formed; only that the requested filename exists.
// This does not ensure that the chart is well-formed; only that the requested chart exists.
//
// Order of resolution:
// - relative to current working directory
// - if path is absolute or begins with '.', error out here
// - chart repos in $HELM_HOME
// - URL
// - pull from the given URL
//
// If 'verify' is true, this will attempt to also verify the chart.
func (c *ChartPathOptions) LocateChart(name string, settings cli.EnvSettings) (string, error) {
func LocateChart(name string, c ChartPathOptions, settings cli.EnvSettings, repoClient *repo.Client) (*chart.Chart, error) {
name = strings.TrimSpace(name)
version := strings.TrimSpace(c.Version)
if _, err := os.Stat(name); err == nil {
abs, err := filepath.Abs(name)
if err != nil {
return abs, err
return nil, err
}
if c.Verify {
if _, err := downloader.VerifyChart(abs, c.Keyring); err != nil {
return "", err
if _, err := VerifyChart(abs, c.Keyring); err != nil {
return nil, err
}
}
return abs, nil
return loader.Load(abs)
}
if filepath.IsAbs(name) || strings.HasPrefix(name, ".") {
return name, errors.Errorf("path %q not found", name)
return nil, errors.Errorf("path %q not found", name)
}
crepo := filepath.Join(settings.Home.Repository(), name)
if _, err := os.Stat(crepo); err == nil {
return filepath.Abs(crepo)
ref, err := repo.ParseNameTag(name, version)
if err != nil {
return nil, err
}
dl := downloader.ChartDownloader{
HelmHome: settings.Home,
Out: os.Stdout,
Keyring: c.Keyring,
Getters: getter.All(settings),
Username: c.Username,
Password: c.Password,
}
if c.Verify {
dl.Verify = downloader.VerifyAlways
}
if c.RepoURL != "" {
chartURL, err := repo.FindChartInAuthRepoURL(c.RepoURL, c.Username, c.Password, name, version,
c.CertFile, c.KeyFile, c.CaFile, getter.All(settings))
if err != nil {
return "", err
}
name = chartURL
}
if _, err := os.Stat(settings.Home.Archive()); os.IsNotExist(err) {
os.MkdirAll(settings.Home.Archive(), 0744)
// TODO(bacongobbler): plug into pkg/repo or oras for signing during a pull
//
// see comment in pkg/repo/client.go#PullChart
}
filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive())
if err == nil {
lname, err := filepath.Abs(filename)
if err != nil {
return filename, err
}
return lname, nil
} else if settings.Debug {
return filename, err
if err := repoClient.PullChart(ref); err != nil {
return nil, fmt.Errorf("failed to download %q: %v", ref.String(), err)
}
return filename, errors.Errorf("failed to download %q (hint: running `helm repo update` may help)", name)
return repoClient.LoadChart(ref)
}
// MergeValues merges values from files specified via -f/--values and

@ -19,7 +19,6 @@ package action
import (
"fmt"
"io/ioutil"
"os"
"syscall"
"github.com/Masterminds/semver"
@ -36,6 +35,7 @@ import (
//
// It provides the implementation of 'helm package'.
type Package struct {
cfg *Configuration
ValueOptions
Sign bool
@ -43,37 +43,37 @@ type Package struct {
Keyring string
Version string
AppVersion string
Destination string
Registry string
DependencyUpdate bool
}
// NewPackage creates a new Package object with the given configuration.
func NewPackage() *Package {
return &Package{}
func NewPackage(cfg *Configuration) *Package {
return &Package{cfg: cfg}
}
// Run executes 'helm package' against the given chart and returns the path to the packaged chart.
func (p *Package) Run(path string) (string, error) {
func (p *Package) Run(path string) error {
ch, err := loader.LoadDir(path)
if err != nil {
return "", err
return err
}
validChartType, err := chartutil.IsValidChartType(ch)
if !validChartType {
return "", err
return err
}
combinedVals, err := chartutil.CoalesceValues(ch, p.ValueOptions.rawValues)
if err != nil {
return "", err
return err
}
ch.Values = combinedVals
// If version is set, modify the version.
if p.Version != "" {
if err := setVersion(ch, p.Version); err != nil {
return "", err
return err
}
}
@ -83,32 +83,19 @@ func (p *Package) Run(path string) (string, error) {
if reqs := ch.Metadata.Dependencies; reqs != nil {
if err := CheckDependencies(ch, reqs); err != nil {
return "", err
}
}
var dest string
if p.Destination == "." {
// Save to the current working directory.
dest, err = os.Getwd()
if err != nil {
return "", err
return err
}
} else {
// Otherwise save to set destination
dest = p.Destination
}
name, err := chartutil.Save(ch, dest)
if err != nil {
return "", errors.Wrap(err, "failed to save")
if err := p.cfg.RegistryClient.SaveChart(ch, p.Registry); err != nil {
return errors.Wrap(err, "failed to save")
}
if p.Sign {
err = p.Clearsign(name)
// TODO(bacongobbler): tie into oras/notary for signing OCI manifests
}
return "", err
return nil
}
func setVersion(ch *chart.Chart, ver string) error {

@ -27,8 +27,6 @@ import (
"helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/cli"
"helm.sh/helm/pkg/downloader"
"helm.sh/helm/pkg/getter"
"helm.sh/helm/pkg/repo"
)
@ -36,6 +34,7 @@ import (
//
// It provides the implementation of 'helm pull'.
type Pull struct {
cfg *Configuration
ChartPathOptions
Settings cli.EnvSettings // TODO: refactor this out of pkg/action
@ -48,32 +47,34 @@ type Pull struct {
}
// NewPull creates a new Pull object with the given configuration.
func NewPull() *Pull {
return &Pull{}
func NewPull(cfg *Configuration) *Pull {
return &Pull{cfg: cfg}
}
// Run executes 'helm pull' against the given release.
func (p *Pull) Run(chartRef string) (string, error) {
var out strings.Builder
c := downloader.ChartDownloader{
HelmHome: p.Settings.Home,
Out: &out,
Keyring: p.Keyring,
Verify: downloader.VerifyNever,
Getters: getter.All(p.Settings),
Username: p.Username,
Password: p.Password,
ref, err := repo.ParseNameTag(chartRef, p.Version)
if err != nil {
return out.String(), err
}
if p.Verify {
c.Verify = downloader.VerifyAlways
} else if p.VerifyLater {
c.Verify = downloader.VerifyLater
// TODO(bacongobbler): plug into pkg/repo or oras for signing during a pull
//
// see comment in pkg/repo/client.go#PullChart
}
if err := p.cfg.RegistryClient.PullChart(ref); err != nil {
return out.String(), fmt.Errorf("failed to download %q: %v", ref.String(), err)
}
ch, err := p.cfg.RegistryClient.LoadChart(ref)
if err != nil {
return out.String(), err
}
// If untar is set, we fetch to a tempdir, then untar and copy after
// verification.
dest := p.DestDir
if p.Untar {
var err error
@ -84,23 +85,11 @@ func (p *Pull) Run(chartRef string) (string, error) {
defer os.RemoveAll(dest)
}
if p.RepoURL != "" {
chartURL, err := repo.FindChartInAuthRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, getter.All(p.Settings))
if err != nil {
return out.String(), err
}
chartRef = chartURL
}
saved, v, err := c.DownloadTo(chartRef, p.Version, dest)
saved, err := chartutil.Save(ch, dest)
if err != nil {
return out.String(), err
}
if p.Verify {
fmt.Fprintf(&out, "Verification: %v\n", v)
}
// After verification, untar the chart into the requested directory.
if p.Untar {
ud := p.UntarDir
@ -118,5 +107,6 @@ func (p *Pull) Run(chartRef string) (string, error) {
return out.String(), chartutil.ExpandFile(ud, saved)
}
return out.String(), nil
}

@ -23,7 +23,6 @@ import (
"github.com/ghodss/yaml"
"helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chart/loader"
)
type ShowOutputFormat string
@ -57,12 +56,8 @@ func NewShow(output ShowOutputFormat) *Show {
}
// Run executes 'helm show' against the given release.
func (s *Show) Run(chartpath string) (string, error) {
func (s *Show) Run(chrt *chart.Chart) (string, error) {
var out strings.Builder
chrt, err := loader.Load(chartpath)
if err != nil {
return "", err
}
cf, err := yaml.Marshal(chrt.Metadata)
if err != nil {
return "", err

@ -20,12 +20,19 @@ import (
"io/ioutil"
"strings"
"testing"
"helm.sh/helm/pkg/chart/loader"
)
func TestShow(t *testing.T) {
client := NewShow(ShowAll)
output, err := client.Run("../../cmd/helm/testdata/testcharts/alpine")
ch, err := loader.Load("../../cmd/helm/testdata/testcharts/alpine")
if err != nil {
t.Fatal(err)
}
output, err := client.Run(ch)
if err != nil {
t.Fatal(err)
}
@ -65,7 +72,13 @@ func TestShow(t *testing.T) {
// Regression tests for missing values. See issue #1024.
client.OutputFormat = ShowValues
output, err = client.Run("../../cmd/helm/testdata/testcharts/novals")
ch2, err := loader.Load("../../cmd/helm/testdata/testcharts/novals")
if err != nil {
t.Fatal(err)
}
output, err = client.Run(ch2)
if err != nil {
t.Fatal(err)
}

@ -17,7 +17,13 @@ limitations under the License.
package action
import (
"helm.sh/helm/pkg/downloader"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"helm.sh/helm/pkg/provenance"
)
// Verify is the action for building a given chart's Verify tree.
@ -34,6 +40,32 @@ func NewVerify() *Verify {
// Run executes 'helm verify'.
func (v *Verify) Run(chartfile string) error {
_, err := downloader.VerifyChart(chartfile, v.Keyring)
_, err := VerifyChart(chartfile, v.Keyring)
return err
}
// VerifyChart takes a path to a chart archive and a keyring, and verifies the chart.
//
// It assumes that a chart archive file is accompanied by a provenance file whose
// name is the archive file name plus the ".prov" extension.
func VerifyChart(path, keyring string) (*provenance.Verification, error) {
// For now, error out if it's not a tar file.
if fi, err := os.Stat(path); err != nil {
return nil, err
} else if fi.IsDir() {
return nil, errors.New("unpacked charts cannot be verified")
} else if strings.ToLower(filepath.Ext(path)) != ".tgz" {
return nil, errors.New("chart must be a tgz file")
}
provfile := path + ".prov"
if _, err := os.Stat(provfile); err != nil {
return nil, errors.Wrapf(err, "could not load provenance file %s", provfile)
}
sig, err := provenance.NewFromKeyring(keyring, "")
if err != nil {
return nil, errors.Wrap(err, "failed to load keyring")
}
return sig.Verify(path, provfile)
}

@ -0,0 +1,31 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import "testing"
func TestVerifyChart(t *testing.T) {
v, err := VerifyChart("testdata/signtest-0.1.0.tgz", "testdata/helm-test-key.pub")
if err != nil {
t.Fatal(err)
}
// The verification is tested at length in the provenance package. Here,
// we just want a quick sanity check that the v is not empty.
if len(v.FileHash) == 0 {
t.Error("Digest missing")
}
}

@ -15,8 +15,8 @@ limitations under the License.
package chart
// APIVersionv1 is the API version number for version 1.
const APIVersionv1 = "v1"
// APIVersionV1 is the API version number for version 1.
const APIVersionV1 = "v1"
// Chart is a helm package that contains metadata, a default config, zero or more
// optionally parameterizable templates, and zero or more charts (dependencies).
@ -96,3 +96,7 @@ func (ch *Chart) ChartFullPath() string {
}
return ch.Name()
}
func (ch *Chart) Validate() error {
return ch.Metadata.Validate()
}

@ -31,11 +31,6 @@ type Dependency struct {
// A lock file will always produce a single version, while a dependency
// may contain a semantic version range.
Version string `json:"version,omitempty"`
// The URL to the repository.
//
// Appending `index.yaml` to this string should result in a URL that can be
// used to fetch the repository index.
Repository string `json:"repository"`
// A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled )
Condition string `json:"condition,omitempty"`
// Tags can be used to group charts for enabling/disabling together

@ -106,12 +106,8 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
}
}
// Ensure that we got a Chart.yaml file
if c.Metadata == nil {
return c, errors.New("chart metadata (Chart.yaml) missing")
}
if c.Name() == "" {
return c, errors.New("invalid chart (Chart.yaml): name must not be empty")
if err := c.Validate(); err != nil {
return c, err
}
for n, files := range subcharts {

@ -109,7 +109,7 @@ icon: https://example.com/64x64.png
if err == nil {
t.Fatal("Expected err to be non-nil")
}
if err.Error() != "chart metadata (Chart.yaml) missing" {
if err.Error() != "metadata is required" {
t.Errorf("Expected chart metadata missing error, got '%s'", err.Error())
}
}
@ -180,8 +180,8 @@ func verifyDependencies(t *testing.T, c *chart.Chart) {
t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
}
tests := []*chart.Dependency{
{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
{Name: "alpine", Version: "0.1.0"},
{Name: "mariner", Version: "4.3.2"},
}
for i, tt := range tests {
d := c.Metadata.Dependencies[i]
@ -191,9 +191,6 @@ func verifyDependencies(t *testing.T, c *chart.Chart) {
if d.Version != tt.Version {
t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version)
}
if d.Repository != tt.Repository {
t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository)
}
}
}
@ -202,8 +199,8 @@ func verifyDependenciesLock(t *testing.T, c *chart.Chart) {
t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
}
tests := []*chart.Dependency{
{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
{Name: "alpine", Version: "0.1.0"},
{Name: "mariner", Version: "4.3.2"},
}
for i, tt := range tests {
d := c.Metadata.Dependencies[i]
@ -213,9 +210,6 @@ func verifyDependenciesLock(t *testing.T, c *chart.Chart) {
if d.Version != tt.Version {
t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version)
}
if d.Repository != tt.Repository {
t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository)
}
}
}

@ -1,3 +1,4 @@
apiVersion: v1
name: albatross
description: A Helm chart for Kubernetes
version: 0.1.0

Binary file not shown.

@ -1,3 +1,4 @@
apiVersion: v1
name: alpine
description: Deploy a basic Alpine Linux pod
version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
name: mast1
description: A Helm chart for Kubernetes
version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
name: alpine
description: Deploy a basic Alpine Linux pod
version: 0.1.0

@ -1,3 +1,4 @@
apiVersion: v1
name: mast1
description: A Helm chart for Kubernetes
version: 0.1.0

@ -6,7 +6,9 @@ tar -zcvf mariner/charts/albatross-0.1.0.tgz albatross
echo "Packing mariner into frobnitz"
tar -zcvf frobnitz/charts/mariner-4.3.2.tgz mariner
tar -zcvf frobnitz_backslash/charts/mariner-4.3.2.tgz mariner
# Pack the frobnitz chart.
echo "Packing frobnitz"
tar --exclude=ignore/* -zcvf frobnitz-1.2.3.tgz frobnitz
tar --exclude=ignore/* -zcvf frobnitz_backslash-1.2.3.tgz frobnitz_backslash

@ -1,3 +1,4 @@
apiVersion: v1
name: mariner
description: A Helm chart for Kubernetes
version: 4.3.2

@ -15,6 +15,8 @@ limitations under the License.
package chart
import "errors"
// Maintainer describes a Chart maintainer.
type Maintainer struct {
// Name is a user name or organization name
@ -65,3 +67,20 @@ type Metadata struct {
// Specifies the chart type: application or library
Type string `json:"type,omitempty"`
}
func (md *Metadata) Validate() error {
if md == nil {
return errors.New("metadata is required")
}
if md.APIVersion == "" {
return errors.New("metadata apiVersion is required")
}
if md.Name == "" {
return errors.New("metadata name is required")
}
if md.Version == "" {
return errors.New("metadata version is required")
}
// TODO validate valid semver here?
return nil
}

@ -40,8 +40,8 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) {
}
// Api instead of API because it was generated via protobuf.
if f.APIVersion != chart.APIVersionv1 {
t.Errorf("Expected API Version %q, got %q", chart.APIVersionv1, f.APIVersion)
if f.APIVersion != chart.APIVersionV1 {
t.Errorf("Expected API Version %q, got %q", chart.APIVersionV1, f.APIVersion)
}
if f.Name != name {

@ -34,7 +34,11 @@ func TestCreate(t *testing.T) {
}
defer os.RemoveAll(tdir)
cf := &chart.Metadata{Name: "foo"}
cf := &chart.Metadata{
APIVersion: chart.APIVersionV1,
Name: "foo",
Version: "0.1.0",
}
c, err := Create(cf, tdir)
if err != nil {
@ -85,7 +89,11 @@ func TestCreateFrom(t *testing.T) {
}
defer os.RemoveAll(tdir)
cf := &chart.Metadata{Name: "foo"}
cf := &chart.Metadata{
APIVersion: chart.APIVersionV1,
Name: "foo",
Version: "0.1.0",
}
srcdir := "./testdata/mariner"
if err := CreateFrom(cf, tdir, srcdir); err != nil {

@ -27,6 +27,7 @@ import (
)
func loadChart(t *testing.T, path string) *chart.Chart {
t.Helper()
c, err := loader.Load(path)
if err != nil {
t.Fatalf("failed to load testdata: %s", err)
@ -36,8 +37,8 @@ func loadChart(t *testing.T, path string) *chart.Chart {
func TestLoadDependency(t *testing.T) {
tests := []*chart.Dependency{
{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
{Name: "alpine", Version: "0.1.0"},
{Name: "mariner", Version: "4.3.2"},
}
check := func(deps []*chart.Dependency) {
@ -51,9 +52,6 @@ func TestLoadDependency(t *testing.T) {
if deps[i].Version != tt.Version {
t.Errorf("expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, deps[i].Version)
}
if deps[i].Repository != tt.Repository {
t.Errorf("expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, deps[i].Repository)
}
}
}
c := loadChart(t, "testdata/frobnitz")

@ -20,6 +20,7 @@ import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
@ -93,6 +94,23 @@ func SaveDir(c *chart.Chart, dest string) error {
return nil
}
// SaveToWriter archives the chart and writes to w.
func SaveToWriter(c *chart.Chart, w io.Writer) error {
// Wrap in gzip writer
zipper := gzip.NewWriter(w)
zipper.Header.Extra = headerBytes
zipper.Header.Comment = "Helm"
// Wrap in tar writer
twriter := tar.NewWriter(zipper)
defer func() {
twriter.Close()
zipper.Close()
}()
return writeTarContents(twriter, c, "")
}
// Save creates an archived chart to the given directory.
//
// This takes an existing chart and a destination directory.
@ -109,18 +127,11 @@ func Save(c *chart.Chart, outDir string) (string, error) {
return "", errors.Errorf("location %s is not a directory", outDir)
}
if c.Metadata == nil {
return "", errors.New("no Chart.yaml data")
}
cfile := c.Metadata
if cfile.Name == "" {
return "", errors.New("no chart name specified (Chart.yaml)")
} else if cfile.Version == "" {
return "", errors.New("no chart version specified (Chart.yaml)")
if err := c.Validate(); err != nil {
return "", errors.Wrap(err, "chart validation")
}
filename := fmt.Sprintf("%s-%s.tgz", cfile.Name, cfile.Version)
filename := fmt.Sprintf("%s-%s.tgz", c.Name(), c.Metadata.Version)
filename = filepath.Join(outDir, filename)
if stat, err := os.Stat(filepath.Dir(filename)); os.IsNotExist(err) {
if err := os.MkdirAll(filepath.Dir(filename), 0755); !os.IsExist(err) {
@ -135,27 +146,15 @@ func Save(c *chart.Chart, outDir string) (string, error) {
return "", err
}
// Wrap in gzip writer
zipper := gzip.NewWriter(f)
zipper.Header.Extra = headerBytes
zipper.Header.Comment = "Helm"
// Wrap in tar writer
twriter := tar.NewWriter(zipper)
rollback := false
defer func() {
twriter.Close()
zipper.Close()
f.Close()
if rollback {
os.Remove(filename)
}
}()
if err := writeTarContents(twriter, c, ""); err != nil {
rollback = true
}
return filename, err
return filename, SaveToWriter(c, f)
}
func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error {

@ -35,8 +35,9 @@ func TestSave(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{
Name: "ahab",
Version: "1.2.3.4",
APIVersion: chart.APIVersionV1,
Name: "ahab",
Version: "1.2.3",
},
Files: []*chart.File{
{Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")},
@ -80,8 +81,9 @@ func TestSaveDir(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{
Name: "ahab",
Version: "1.2.3.4",
APIVersion: chart.APIVersionV1,
Name: "ahab",
Version: "1.2.3",
},
Files: []*chart.File{
{Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")},

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save