Merge pull request #5365 from bacongobbler/remove-pkg-tiller

ref: remove pkg/helm, pkg/hapi, pkg/tiller
pull/5440/head
Matthew Fisher 7 years ago committed by GitHub
commit e5094169d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -35,7 +35,7 @@ func TestCreateCmd(t *testing.T) {
cname := "testchart" cname := "testchart"
// Run a create // Run a create
if _, err := executeCommand(nil, "create "+cname); err != nil { if _, _, err := executeActionCommand("create " + cname); err != nil {
t.Errorf("Failed to run create: %s", err) t.Errorf("Failed to run create: %s", err)
return return
} }
@ -86,7 +86,7 @@ func TestCreateStarterCmd(t *testing.T) {
defer testChdir(t, tdir)() defer testChdir(t, tdir)()
// Run a create // Run a create
if _, err := executeCommand(nil, fmt.Sprintf("--home='%s' create --starter=starterchart %s", hh.String(), cname)); err != nil { if _, _, err := executeActionCommand(fmt.Sprintf("--home='%s' create --starter=starterchart %s", hh.String(), cname)); err != nil {
t.Errorf("Failed to run create: %s", err) t.Errorf("Failed to run create: %s", err)
return return
} }

@ -16,18 +16,13 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"io" "io"
"os"
"path/filepath" "path/filepath"
"github.com/Masterminds/semver"
"github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/chart/loader"
) )
const dependencyDesc = ` const dependencyDesc = `
@ -103,14 +98,8 @@ func newDependencyCmd(out io.Writer) *cobra.Command {
return cmd return cmd
} }
type dependencyLisOptions struct {
chartpath string
}
func newDependencyListCmd(out io.Writer) *cobra.Command { func newDependencyListCmd(out io.Writer) *cobra.Command {
o := &dependencyLisOptions{ client := action.NewDependency()
chartpath: ".",
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "list CHART", Use: "list CHART",
@ -119,151 +108,12 @@ func newDependencyListCmd(out io.Writer) *cobra.Command {
Long: dependencyListDesc, Long: dependencyListDesc,
Args: require.MaximumNArgs(1), Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
chartpath := "."
if len(args) > 0 { if len(args) > 0 {
o.chartpath = filepath.Clean(args[0]) chartpath = filepath.Clean(args[0])
} }
return o.run(out) return client.List(chartpath, out)
}, },
} }
return cmd return cmd
} }
func (o *dependencyLisOptions) run(out io.Writer) error {
c, err := loader.Load(o.chartpath)
if err != nil {
return err
}
if c.Metadata.Dependencies == nil {
fmt.Fprintf(out, "WARNING: no dependencies at %s\n", filepath.Join(o.chartpath, "charts"))
return nil
}
o.printDependencies(out, c.Metadata.Dependencies)
fmt.Fprintln(out)
o.printMissing(out, c.Metadata.Dependencies)
return nil
}
func (o *dependencyLisOptions) dependencyStatus(dep *chart.Dependency) string {
filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*")
archives, err := filepath.Glob(filepath.Join(o.chartpath, "charts", filename))
if err != nil {
return "bad pattern"
} else if len(archives) > 1 {
return "too many matches"
} else if len(archives) == 1 {
archive := archives[0]
if _, err := os.Stat(archive); err == nil {
c, err := loader.Load(archive)
if err != nil {
return "corrupt"
}
if c.Name() != dep.Name {
return "misnamed"
}
if c.Metadata.Version != dep.Version {
constraint, err := semver.NewConstraint(dep.Version)
if err != nil {
return "invalid version"
}
v, err := semver.NewVersion(c.Metadata.Version)
if err != nil {
return "invalid version"
}
if constraint.Check(v) {
return "ok"
}
return "wrong version"
}
return "ok"
}
}
folder := filepath.Join(o.chartpath, "charts", dep.Name)
if fi, err := os.Stat(folder); err != nil {
return "missing"
} else if !fi.IsDir() {
return "mispackaged"
}
c, err := loader.Load(folder)
if err != nil {
return "corrupt"
}
if c.Name() != dep.Name {
return "misnamed"
}
if c.Metadata.Version != dep.Version {
constraint, err := semver.NewConstraint(dep.Version)
if err != nil {
return "invalid version"
}
v, err := semver.NewVersion(c.Metadata.Version)
if err != nil {
return "invalid version"
}
if constraint.Check(v) {
return "unpacked"
}
return "wrong version"
}
return "unpacked"
}
// printDependencies prints all of the dependencies in the yaml file.
func (o *dependencyLisOptions) printDependencies(out io.Writer, reqs []*chart.Dependency) {
table := uitable.New()
table.MaxColWidth = 80
table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS")
for _, row := range reqs {
table.AddRow(row.Name, row.Version, row.Repository, o.dependencyStatus(row))
}
fmt.Fprintln(out, table)
}
// printMissing prints warnings about charts that are present on disk, but are
// not in Charts.yaml.
func (o *dependencyLisOptions) printMissing(out io.Writer, reqs []*chart.Dependency) {
folder := filepath.Join(o.chartpath, "charts/*")
files, err := filepath.Glob(folder)
if err != nil {
fmt.Fprintln(out, err)
return
}
for _, f := range files {
fi, err := os.Stat(f)
if err != nil {
fmt.Fprintf(out, "Warning: %s\n", err)
}
// Skip anything that is not a directory and not a tgz file.
if !fi.IsDir() && filepath.Ext(f) != ".tgz" {
continue
}
c, err := loader.Load(f)
if err != nil {
fmt.Fprintf(out, "WARNING: %q is not a chart.\n", f)
continue
}
found := false
for _, d := range reqs {
if d.Name == c.Name() {
found = true
break
}
}
if !found {
fmt.Fprintf(out, "WARNING: %q is not in Chart.yaml.\n", f)
}
}
}

@ -17,10 +17,14 @@ package main
import ( import (
"io" "io"
"os"
"path/filepath"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/client-go/util/homedir"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/downloader" "k8s.io/helm/pkg/downloader"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
) )
@ -36,17 +40,8 @@ If no lock file is found, 'helm dependency build' will mirror the behavior
of 'helm dependency update'. of 'helm dependency update'.
` `
type dependencyBuildOptions struct {
keyring string // --keyring
verify bool // --verify
chartpath string
}
func newDependencyBuildCmd(out io.Writer) *cobra.Command { func newDependencyBuildCmd(out io.Writer) *cobra.Command {
o := &dependencyBuildOptions{ client := action.NewDependency()
chartpath: ".",
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "build CHART", Use: "build CHART",
@ -54,31 +49,38 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
Long: dependencyBuildDesc, Long: dependencyBuildDesc,
Args: require.MaximumNArgs(1), Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
chartpath := "."
if len(args) > 0 { if len(args) > 0 {
o.chartpath = args[0] chartpath = filepath.Clean(args[0])
}
man := &downloader.Manager{
Out: out,
ChartPath: chartpath,
HelmHome: settings.Home,
Keyring: client.Keyring,
Getters: getter.All(settings),
}
if client.Verify {
man.Verify = downloader.VerifyIfPossible
} }
return o.run(out) if settings.Debug {
man.Debug = true
}
return man.Build()
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&o.verify, "verify", false, "verify the packages against signatures") f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures")
f.StringVar(&o.keyring, "keyring", defaultKeyring(), "keyring containing public keys") f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
return cmd return cmd
} }
func (o *dependencyBuildOptions) run(out io.Writer) error { // defaultKeyring returns the expanded path to the default keyring.
man := &downloader.Manager{ func defaultKeyring() string {
Out: out, if v, ok := os.LookupEnv("GNUPGHOME"); ok {
ChartPath: o.chartpath, return filepath.Join(v, "pubring.gpg")
HelmHome: settings.Home,
Keyring: o.keyring,
Getters: getter.All(settings),
}
if o.verify {
man.Verify = downloader.VerifyIfPossible
} }
return filepath.Join(homedir.HomeDir(), ".gnupg", "pubring.gpg")
return man.Build()
} }

@ -44,7 +44,7 @@ func TestDependencyBuildCmd(t *testing.T) {
} }
cmd := fmt.Sprintf("--home='%s' dependency build '%s'", hh, hh.Path(chartname)) cmd := fmt.Sprintf("--home='%s' dependency build '%s'", hh, hh.Path(chartname))
out, err := executeCommand(nil, cmd) _, out, err := executeActionCommand(cmd)
// In the first pass, we basically want the same results as an update. // In the first pass, we basically want the same results as an update.
if err != nil { if err != nil {
@ -72,7 +72,7 @@ func TestDependencyBuildCmd(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
out, err = executeCommand(nil, cmd) _, out, err = executeActionCommand(cmd)
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
t.Fatal(err) t.Fatal(err)

@ -22,9 +22,9 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/downloader" "k8s.io/helm/pkg/downloader"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath"
) )
const dependencyUpDesc = ` const dependencyUpDesc = `
@ -42,23 +42,9 @@ reason, an update command will not remove charts unless they are (a) present
in the Chart.yaml file, but (b) at the wrong version. in the Chart.yaml file, but (b) at the wrong version.
` `
// dependencyUpdateOptions describes a 'helm dependency update'
type dependencyUpdateOptions struct {
keyring string // --keyring
skipRefresh bool // --skip-refresh
verify bool // --verify
// args
chartpath string
helmhome helmpath.Home
}
// newDependencyUpdateCmd creates a new dependency update command. // newDependencyUpdateCmd creates a new dependency update command.
func newDependencyUpdateCmd(out io.Writer) *cobra.Command { func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
o := &dependencyUpdateOptions{ client := action.NewDependency()
chartpath: ".",
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "update CHART", Use: "update CHART",
@ -67,37 +53,32 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
Long: dependencyUpDesc, Long: dependencyUpDesc,
Args: require.MaximumNArgs(1), Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
chartpath := "."
if len(args) > 0 { if len(args) > 0 {
o.chartpath = filepath.Clean(args[0]) chartpath = filepath.Clean(args[0])
} }
o.helmhome = settings.Home
return o.run(out)
},
}
f := cmd.Flags()
f.BoolVar(&o.verify, "verify", false, "verify the packages against signatures")
f.StringVar(&o.keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&o.skipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
return cmd
}
// run runs the full dependency update process.
func (o *dependencyUpdateOptions) run(out io.Writer) error {
man := &downloader.Manager{ man := &downloader.Manager{
Out: out, Out: out,
ChartPath: o.chartpath, ChartPath: chartpath,
HelmHome: o.helmhome, HelmHome: settings.Home,
Keyring: o.keyring, Keyring: client.Keyring,
SkipUpdate: o.skipRefresh, SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings), Getters: getter.All(settings),
} }
if o.verify { if client.Verify {
man.Verify = downloader.VerifyAlways man.Verify = downloader.VerifyAlways
} }
if settings.Debug { if settings.Debug {
man.Debug = true man.Debug = true
} }
return man.Update() return man.Update()
},
}
f := cmd.Flags()
f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures")
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
return cmd
} }

@ -16,7 +16,6 @@ limitations under the License.
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -52,7 +51,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
out, err := executeCommand(nil, fmt.Sprintf("--home='%s' dependency update '%s'", hh.String(), hh.Path(chartname))) _, out, err := executeActionCommand(fmt.Sprintf("--home='%s' dependency update '%s'", hh.String(), hh.Path(chartname)))
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
t.Fatal(err) t.Fatal(err)
@ -95,7 +94,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
out, err = executeCommand(nil, fmt.Sprintf("--home='%s' dependency update '%s'", hh, hh.Path(chartname))) _, out, err = executeActionCommand(fmt.Sprintf("--home='%s' dependency update '%s'", hh, hh.Path(chartname)))
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
t.Fatal(err) t.Fatal(err)
@ -133,7 +132,7 @@ func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
out, err := executeCommand(nil, fmt.Sprintf("--home='%s' dependency update --skip-refresh %s", hh, hh.Path(chartname))) _, out, err := executeActionCommand(fmt.Sprintf("--home='%s' dependency update --skip-refresh %s", hh, hh.Path(chartname)))
if err == nil { if err == nil {
t.Fatal("Expected failure to find the repo with skipRefresh") t.Fatal("Expected failure to find the repo with skipRefresh")
} }
@ -164,13 +163,8 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
out := bytes.NewBuffer(nil) _, output, err := executeActionCommand(fmt.Sprintf("--home='%s' dependency update %s", hh, hh.Path(chartname)))
o := &dependencyUpdateOptions{} if err != nil {
o.helmhome = hh
o.chartpath = hh.Path(chartname)
if err := o.run(out); err != nil {
output := out.String()
t.Logf("Output: %s", output) t.Logf("Output: %s", output)
t.Fatal(err) t.Fatal(err)
} }
@ -178,14 +172,14 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
// Chart repo is down // Chart repo is down
srv.Stop() srv.Stop()
if err := o.run(out); err == nil { _, output, err = executeActionCommand(fmt.Sprintf("--home='%s' dependency update %s", hh, hh.Path(chartname)))
output := out.String() if err == nil {
t.Logf("Output: %s", output) t.Logf("Output: %s", output)
t.Fatal("Expected error, got nil") t.Fatal("Expected error, got nil")
} }
// Make sure charts dir still has dependencies // Make sure charts dir still has dependencies
files, err := ioutil.ReadDir(filepath.Join(o.chartpath, "charts")) files, err := ioutil.ReadDir(filepath.Join(hh.Path(chartname), "charts"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -201,7 +195,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
} }
// Make sure tmpcharts is deleted // Make sure tmpcharts is deleted
if _, err := os.Stat(filepath.Join(o.chartpath, "tmpcharts")); !os.IsNotExist(err) { if _, err := os.Stat(filepath.Join(hh.Path(chartname), "tmpcharts")); !os.IsNotExist(err) {
t.Fatalf("tmpcharts dir still exists") t.Fatalf("tmpcharts dir still exists")
} }
} }

@ -22,7 +22,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/action"
) )
var getHelp = ` var getHelp = `
@ -38,16 +38,8 @@ By default, this prints a human readable collection of information about the
chart, the supplied values, and the generated manifest file. chart, the supplied values, and the generated manifest file.
` `
type getOptions struct { func newGetCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
version int // --revision client := action.NewGet(cfg)
release string
client helm.Interface
}
func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
o := &getOptions{client: client}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "get RELEASE_NAME", Use: "get RELEASE_NAME",
@ -55,25 +47,19 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
Long: getHelp, Long: getHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.release = args[0] res, err := client.Run(args[0])
o.client = ensureHelmClient(o.client, false) if err != nil {
return o.run(out) return err
}
return printRelease(out, res)
}, },
} }
cmd.Flags().IntVar(&o.version, "revision", 0, "get the named release with revision") cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
cmd.AddCommand(newGetValuesCmd(client, out)) cmd.AddCommand(newGetValuesCmd(cfg, out))
cmd.AddCommand(newGetManifestCmd(client, out)) cmd.AddCommand(newGetManifestCmd(cfg, out))
cmd.AddCommand(newGetHooksCmd(client, out)) cmd.AddCommand(newGetHooksCmd(cfg, out))
return cmd return cmd
} }
func (g *getOptions) run(out io.Writer) error {
res, err := g.client.ReleaseContent(g.release, g.version)
if err != nil {
return err
}
return printRelease(out, res)
}

@ -23,7 +23,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/action"
) )
const getHooksHelp = ` const getHooksHelp = `
@ -32,14 +32,8 @@ This command downloads hooks for a given release.
Hooks are formatted in YAML and separated by the YAML '---\n' separator. Hooks are formatted in YAML and separated by the YAML '---\n' separator.
` `
type getHooksOptions struct { func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
release string client := action.NewGet(cfg)
client helm.Interface
version int
}
func newGetHooksCmd(client helm.Interface, out io.Writer) *cobra.Command {
o := &getHooksOptions{client: client}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "hooks RELEASE_NAME", Use: "hooks RELEASE_NAME",
@ -47,24 +41,18 @@ func newGetHooksCmd(client helm.Interface, out io.Writer) *cobra.Command {
Long: getHooksHelp, Long: getHooksHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.release = args[0] res, err := client.Run(args[0])
o.client = ensureHelmClient(o.client, false)
return o.run(out)
},
}
cmd.Flags().IntVar(&o.version, "revision", 0, "get the named release with revision")
return cmd
}
func (o *getHooksOptions) run(out io.Writer) error {
res, err := o.client.ReleaseContent(o.release, o.version)
if err != nil { if err != nil {
fmt.Fprintln(out, o.release)
return err return err
} }
for _, hook := range res.Hooks { for _, hook := range res.Hooks {
fmt.Fprintf(out, "---\n# %s\n%s", hook.Name, hook.Manifest) fmt.Fprintf(out, "---\n# %s\n%s", hook.Name, hook.Manifest)
} }
return nil return nil
},
}
cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
return cmd
} }

@ -19,8 +19,7 @@ package main
import ( import (
"testing" "testing"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/release"
"k8s.io/helm/pkg/helm"
) )
func TestGetHooks(t *testing.T) { func TestGetHooks(t *testing.T) {
@ -28,7 +27,7 @@ func TestGetHooks(t *testing.T) {
name: "get hooks with release", name: "get hooks with release",
cmd: "get hooks aeneas", cmd: "get hooks aeneas",
golden: "output/get-hooks.txt", golden: "output/get-hooks.txt",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})},
}, { }, {
name: "get hooks without args", name: "get hooks without args",
cmd: "get hooks", cmd: "get hooks",

@ -23,7 +23,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/action"
) )
var getManifestHelp = ` var getManifestHelp = `
@ -34,16 +34,8 @@ were generated from this release's chart(s). If a chart is dependent on other
charts, those resources will also be included in the manifest. charts, those resources will also be included in the manifest.
` `
type getManifestOptions struct { func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
version int // --revision client := action.NewGet(cfg)
release string
client helm.Interface
}
func newGetManifestCmd(client helm.Interface, out io.Writer) *cobra.Command {
o := &getManifestOptions{client: client}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "manifest RELEASE_NAME", Use: "manifest RELEASE_NAME",
@ -51,22 +43,16 @@ func newGetManifestCmd(client helm.Interface, out io.Writer) *cobra.Command {
Long: getManifestHelp, Long: getManifestHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.release = args[0] res, err := client.Run(args[0])
o.client = ensureHelmClient(o.client, false)
return o.run(out)
},
}
cmd.Flags().IntVar(&o.version, "revision", 0, "get the named release with revision")
return cmd
}
// getManifest implements 'helm get manifest'
func (o *getManifestOptions) run(out io.Writer) error {
res, err := o.client.ReleaseContent(o.release, o.version)
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintln(out, res.Manifest) fmt.Fprintln(out, res.Manifest)
return nil return nil
},
}
cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
return cmd
} }

@ -19,8 +19,7 @@ package main
import ( import (
"testing" "testing"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/release"
"k8s.io/helm/pkg/helm"
) )
func TestGetManifest(t *testing.T) { func TestGetManifest(t *testing.T) {
@ -28,7 +27,7 @@ func TestGetManifest(t *testing.T) {
name: "get manifest with release", name: "get manifest with release",
cmd: "get manifest juno", cmd: "get manifest juno",
golden: "output/get-manifest.txt", golden: "output/get-manifest.txt",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "juno"})}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "juno"})},
}, { }, {
name: "get manifest without args", name: "get manifest without args",
cmd: "get manifest", cmd: "get manifest",

@ -19,8 +19,7 @@ package main
import ( import (
"testing" "testing"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/release"
"k8s.io/helm/pkg/helm"
) )
func TestGetCmd(t *testing.T) { func TestGetCmd(t *testing.T) {
@ -28,7 +27,7 @@ func TestGetCmd(t *testing.T) {
name: "get with a release", name: "get with a release",
cmd: "get thomas-guide", cmd: "get thomas-guide",
golden: "output/get-release.txt", golden: "output/get-release.txt",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"})}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "thomas-guide"})},
}, { }, {
name: "get requires release name arg", name: "get requires release name arg",
cmd: "get", cmd: "get",

@ -20,29 +20,18 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/ghodss/yaml"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/helm"
) )
var getValuesHelp = ` var getValuesHelp = `
This command downloads a values file for a given release. This command downloads a values file for a given release.
` `
type getValuesOptions struct { func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
allValues bool // --all client := action.NewGetValues(cfg)
version int // --revision
release string
client helm.Interface
}
func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command {
o := &getValuesOptions{client: client}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "values RELEASE_NAME", Use: "values RELEASE_NAME",
@ -50,43 +39,17 @@ func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command {
Long: getValuesHelp, Long: getValuesHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.release = args[0] res, err := client.Run(args[0])
o.client = ensureHelmClient(o.client, false)
return o.run(out)
},
}
cmd.Flags().BoolVarP(&o.allValues, "all", "a", false, "dump all (computed) values")
cmd.Flags().IntVar(&o.version, "revision", 0, "get the named release with revision")
return cmd
}
// getValues implements 'helm get values'
func (o *getValuesOptions) run(out io.Writer) error {
res, err := o.client.ReleaseContent(o.release, o.version)
if err != nil {
return err
}
// If the user wants all values, compute the values and return.
if o.allValues {
cfg, err := chartutil.CoalesceValues(res.Chart, res.Config)
if err != nil {
return err
}
cfgStr, err := cfg.YAML()
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintln(out, cfgStr) fmt.Fprintln(out, res)
return nil return nil
},
} }
resConfig, err := yaml.Marshal(res.Config) f := cmd.Flags()
if err != nil { f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
return err f.BoolVarP(&client.AllValues, "all", "a", false, "dump all (computed) values")
} return cmd
fmt.Fprintln(out, string(resConfig))
return nil
} }

@ -19,8 +19,7 @@ package main
import ( import (
"testing" "testing"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/release"
"k8s.io/helm/pkg/helm"
) )
func TestGetValuesCmd(t *testing.T) { func TestGetValuesCmd(t *testing.T) {
@ -28,7 +27,7 @@ func TestGetValuesCmd(t *testing.T) {
name: "get values with a release", name: "get values with a release",
cmd: "get values thomas-guide", cmd: "get values thomas-guide",
golden: "output/get-values.txt", golden: "output/get-values.txt",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"})}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "thomas-guide"})},
}, { }, {
name: "get values requires release name arg", name: "get values requires release name arg",
cmd: "get values", cmd: "get values",

@ -27,15 +27,14 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/helm/pkg/action" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/cli"
"k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/kube" "k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver" "k8s.io/helm/pkg/storage/driver"
) )
var ( var (
settings environment.EnvSettings settings cli.EnvSettings
config genericclioptions.RESTClientGetter config genericclioptions.RESTClientGetter
configOnce sync.Once configOnce sync.Once
) )
@ -52,45 +51,13 @@ func logf(format string, v ...interface{}) {
} }
func main() { func main() {
cmd := newRootCmd(nil, newActionConfig(false), os.Stdout, os.Args[1:]) cmd := newRootCmd(newActionConfig(false), os.Stdout, os.Args[1:])
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
logf("%+v", err) logf("%+v", err)
os.Exit(1) os.Exit(1)
} }
} }
// ensureHelmClient returns a new helm client impl. if h is not nil.
func ensureHelmClient(h helm.Interface, allNamespaces bool) helm.Interface {
if h != nil {
return h
}
return newClient(allNamespaces)
}
func newClient(allNamespaces bool) helm.Interface {
kc := kube.New(kubeConfig())
kc.Log = logf
clientset, err := kc.KubernetesClientSet()
if err != nil {
// TODO return error
log.Fatal(err)
}
var namespace string
if !allNamespaces {
namespace = getNamespace()
}
// TODO add other backends
d := driver.NewSecrets(clientset.CoreV1().Secrets(namespace))
d.Log = logf
return helm.NewClient(
helm.KubeClient(kc),
helm.Driver(d),
helm.Discovery(clientset.Discovery()),
)
}
func newActionConfig(allNamespaces bool) *action.Configuration { func newActionConfig(allNamespaces bool) *action.Configuration {
kc := kube.New(kubeConfig()) kc := kube.New(kubeConfig())
kc.Log = logf kc.Log = logf

@ -30,13 +30,12 @@ import (
"k8s.io/helm/internal/test" "k8s.io/helm/internal/test"
"k8s.io/helm/pkg/action" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/release"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver" "k8s.io/helm/pkg/storage/driver"
"k8s.io/helm/pkg/tiller/environment"
) )
func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() } func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() }
@ -66,11 +65,13 @@ func runTestCmd(t *testing.T, tests []cmdTestCase) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
defer resetEnv()() defer resetEnv()()
c := &helm.FakeClient{ storage := storageFixture()
Rels: tt.rels, for _, rel := range tt.rels {
TestRunStatus: tt.testRunStatus, if err := storage.Create(rel); err != nil {
t.Fatal(err)
}
} }
out, err := executeCommand(c, tt.cmd) _, out, err := executeActionCommandC(storage, tt.cmd)
if (err != nil) != tt.wantError { if (err != nil) != tt.wantError {
t.Errorf("expected error, got '%v'", err) t.Errorf("expected error, got '%v'", err)
} }
@ -115,12 +116,12 @@ func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command,
actionConfig := &action.Configuration{ actionConfig := &action.Configuration{
Releases: store, Releases: store,
KubeClient: &environment.PrintingKubeClient{Out: ioutil.Discard}, KubeClient: &kube.PrintingKubeClient{Out: ioutil.Discard},
Discovery: fake.NewSimpleClientset().Discovery(), Discovery: fake.NewSimpleClientset().Discovery(),
Log: func(format string, v ...interface{}) {}, Log: func(format string, v ...interface{}) {},
} }
root := newRootCmd(nil, actionConfig, buf, args) root := newRootCmd(actionConfig, buf, args)
root.SetOutput(buf) root.SetOutput(buf)
root.SetArgs(args) root.SetArgs(args)
@ -137,34 +138,10 @@ type cmdTestCase struct {
wantError bool wantError bool
// Rels are the available releases at the start of the test. // Rels are the available releases at the start of the test.
rels []*release.Release rels []*release.Release
testRunStatus map[string]release.TestRunStatus
}
// deprecated: Switch to executeActionCommandC
func executeCommand(c helm.Interface, cmd string) (string, error) {
_, output, err := executeCommandC(c, cmd)
return output, err
}
// deprecated: Switch to executeActionCommandC
func executeCommandC(client helm.Interface, cmd string) (*cobra.Command, string, error) {
args, err := shellwords.Parse(cmd)
if err != nil {
return nil, "", err
} }
buf := new(bytes.Buffer)
actionConfig := &action.Configuration{
Releases: storage.Init(driver.NewMemory()),
}
root := newRootCmd(client, actionConfig, buf, args)
root.SetOutput(buf)
root.SetArgs(args)
c, err := root.ExecuteC() func executeActionCommand(cmd string) (*cobra.Command, string, error) {
return executeActionCommandC(storageFixture(), cmd)
return c, buf.String(), err
} }
// ensureTestHome creates a home directory like ensureHome, but without remote references. // ensureTestHome creates a home directory like ensureHome, but without remote references.

@ -17,31 +17,15 @@ limitations under the License.
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"github.com/ghodss/yaml"
"github.com/gosuri/uitable"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
) )
type releaseInfo struct {
Revision int `json:"revision"`
Updated string `json:"updated"`
Status string `json:"status"`
Chart string `json:"chart"`
Description string `json:"description"`
}
type releaseHistory []releaseInfo
var historyHelp = ` var historyHelp = `
History prints historical revisions for a given release. History prints historical revisions for a given release.
@ -58,18 +42,8 @@ The historical release set is printed as a formatted table, e.g:
4 Mon Oct 3 10:15:13 2016 deployed alpine-0.1.0 Upgraded successfully 4 Mon Oct 3 10:15:13 2016 deployed alpine-0.1.0 Upgraded successfully
` `
type historyOptions struct { func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
colWidth uint // --col-width client := action.NewHistory(cfg)
max int // --max
outputFormat string // --output
release string
client helm.Interface
}
func newHistoryCmd(c helm.Interface, out io.Writer) *cobra.Command {
o := &historyOptions{client: c}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "history RELEASE_NAME", Use: "history RELEASE_NAME",
@ -78,94 +52,18 @@ func newHistoryCmd(c helm.Interface, out io.Writer) *cobra.Command {
Aliases: []string{"hist"}, Aliases: []string{"hist"},
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.client = ensureHelmClient(o.client, false) history, err := client.Run(args[0])
o.release = args[0]
return o.run(out)
},
}
f := cmd.Flags()
f.IntVar(&o.max, "max", 256, "maximum number of revision to include in history")
f.UintVar(&o.colWidth, "col-width", 60, "specifies the max column width of output")
f.StringVarP(&o.outputFormat, "output", "o", "table", "prints the output in the specified format (json|table|yaml)")
return cmd
}
func (o *historyOptions) run(out io.Writer) error {
rels, err := o.client.ReleaseHistory(o.release, o.max)
if err != nil { if err != nil {
return err return err
} }
if len(rels) == 0 { fmt.Fprintln(out, history)
return nil return nil
},
} }
releaseHistory := getReleaseHistory(rels) f := cmd.Flags()
f.StringVarP(&client.OutputFormat, "output", "o", action.Table.String(), "prints the output in the specified format (json|table|yaml)")
var history []byte f.IntVar(&client.Max, "max", 256, "maximum number of revision to include in history")
var formattingError error
switch o.outputFormat {
case "yaml":
history, formattingError = yaml.Marshal(releaseHistory)
case "json":
history, formattingError = json.Marshal(releaseHistory)
case "table":
history = formatAsTable(releaseHistory, o.colWidth)
default:
return errors.Errorf("unknown output format %q", o.outputFormat)
}
if formattingError != nil {
return formattingError
}
fmt.Fprintln(out, string(history))
return nil
}
func getReleaseHistory(rls []*release.Release) (history releaseHistory) {
for i := len(rls) - 1; i >= 0; i-- {
r := rls[i]
c := formatChartname(r.Chart)
s := r.Info.Status.String()
v := r.Version
d := r.Info.Description
rInfo := releaseInfo{
Revision: v,
Status: s,
Chart: c,
Description: d,
}
if !r.Info.LastDeployed.IsZero() {
rInfo.Updated = r.Info.LastDeployed.String()
}
history = append(history, rInfo)
}
return history
}
func formatAsTable(releases releaseHistory, colWidth uint) []byte {
tbl := uitable.New()
tbl.MaxColWidth = colWidth
tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "DESCRIPTION")
for i := 0; i <= len(releases)-1; i++ {
r := releases[i]
tbl.AddRow(r.Revision, r.Updated, r.Status, r.Chart, r.Description)
}
return tbl.Bytes()
}
func formatChartname(c *chart.Chart) string { return cmd
if c == nil || c.Metadata == nil {
// This is an edge case that has happened in prod, though we don't
// know how: https://github.com/helm/helm/issues/1347
return "MISSING"
}
return fmt.Sprintf("%s-%s", c.Name(), c.Metadata.Version)
} }

@ -19,13 +19,12 @@ package main
import ( import (
"testing" "testing"
rpb "k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/release"
"k8s.io/helm/pkg/helm"
) )
func TestHistoryCmd(t *testing.T) { func TestHistoryCmd(t *testing.T) {
mk := func(name string, vers int, status rpb.Status) *rpb.Release { mk := func(name string, vers int, status release.Status) *release.Release {
return helm.ReleaseMock(&helm.MockReleaseOptions{ return release.Mock(&release.MockReleaseOptions{
Name: name, Name: name,
Version: vers, Version: vers,
Status: status, Status: status,
@ -35,35 +34,35 @@ func TestHistoryCmd(t *testing.T) {
tests := []cmdTestCase{{ tests := []cmdTestCase{{
name: "get history for release", name: "get history for release",
cmd: "history angry-bird", cmd: "history angry-bird",
rels: []*rpb.Release{ rels: []*release.Release{
mk("angry-bird", 4, rpb.StatusDeployed), mk("angry-bird", 4, release.StatusDeployed),
mk("angry-bird", 3, rpb.StatusSuperseded), mk("angry-bird", 3, release.StatusSuperseded),
mk("angry-bird", 2, rpb.StatusSuperseded), mk("angry-bird", 2, release.StatusSuperseded),
mk("angry-bird", 1, rpb.StatusSuperseded), mk("angry-bird", 1, release.StatusSuperseded),
}, },
golden: "output/history.txt", golden: "output/history.txt",
}, { }, {
name: "get history with max limit set", name: "get history with max limit set",
cmd: "history angry-bird --max 2", cmd: "history angry-bird --max 2",
rels: []*rpb.Release{ rels: []*release.Release{
mk("angry-bird", 4, rpb.StatusDeployed), mk("angry-bird", 4, release.StatusDeployed),
mk("angry-bird", 3, rpb.StatusSuperseded), mk("angry-bird", 3, release.StatusSuperseded),
}, },
golden: "output/history-limit.txt", golden: "output/history-limit.txt",
}, { }, {
name: "get history with yaml output format", name: "get history with yaml output format",
cmd: "history angry-bird --output yaml", cmd: "history angry-bird --output yaml",
rels: []*rpb.Release{ rels: []*release.Release{
mk("angry-bird", 4, rpb.StatusDeployed), mk("angry-bird", 4, release.StatusDeployed),
mk("angry-bird", 3, rpb.StatusSuperseded), mk("angry-bird", 3, release.StatusSuperseded),
}, },
golden: "output/history.yaml", golden: "output/history.yaml",
}, { }, {
name: "get history with json output format", name: "get history with json output format",
cmd: "history angry-bird --output json", cmd: "history angry-bird --output json",
rels: []*rpb.Release{ rels: []*release.Release{
mk("angry-bird", 4, rpb.StatusDeployed), mk("angry-bird", 4, release.StatusDeployed),
mk("angry-bird", 3, rpb.StatusSuperseded), mk("angry-bird", 3, release.StatusSuperseded),
}, },
golden: "output/history.json", golden: "output/history.json",
}} }}

@ -29,7 +29,7 @@ import (
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/plugin" "k8s.io/helm/pkg/plugin"
"k8s.io/helm/pkg/plugin/installer" "k8s.io/helm/pkg/plugin/installer"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"

@ -21,7 +21,7 @@ import (
"os" "os"
"testing" "testing"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
) )
const testPluginsFile = "testdata/plugins.yaml" const testPluginsFile = "testdata/plugins.yaml"

@ -17,29 +17,19 @@ limitations under the License.
package main package main
import ( import (
"bytes"
"fmt"
"io" "io"
"path/filepath"
"regexp" "k8s.io/helm/pkg/release"
"strings"
"text/tabwriter"
"text/template"
"time"
"github.com/Masterminds/sprig"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/action" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/downloader" "k8s.io/helm/pkg/downloader"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
) )
const installDesc = ` const installDesc = `
@ -104,268 +94,119 @@ To see the list of chart repositories, use 'helm repo list'. To search for
charts in a repository, use 'helm search'. charts in a repository, use 'helm search'.
` `
type installOptions struct {
name string // arg 0
dryRun bool // --dry-run
disableHooks bool // --disable-hooks
replace bool // --replace
nameTemplate string // --name-template
timeout int64 // --timeout
wait bool // --wait
devel bool // --devel
depUp bool // --dep-up
chartPath string // arg 1
generateName bool // --generate-name
valuesOptions
chartPathOptions
cfg *action.Configuration
// LEGACY: Here until we get upgrade converted
client helm.Interface
}
func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
o := &installOptions{cfg: cfg} client := action.NewInstall(cfg)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "install [NAME] [CHART]", Use: "install [NAME] [CHART]",
Short: "install a chart", Short: "install a chart",
Long: installDesc, Long: installDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
debug("Original chart version: %q", o.version) rel, err := runInstall(args, client, out)
if o.version == "" && o.devel {
debug("setting version to >0.0.0-0")
o.version = ">0.0.0-0"
}
name, chart, err := o.nameAndChart(args)
if err != nil {
return err
}
o.name = name // FIXME
cp, err := o.locateChart(chart)
if err != nil { if err != nil {
return err return err
} }
o.chartPath = cp action.PrintRelease(out, rel)
return nil
return o.run(out)
}, },
} }
f := cmd.Flags() addInstallFlags(cmd.Flags(), client)
f.BoolVarP(&o.generateName, "generate-name", "g", false, "generate the name (and omit the NAME parameter)")
f.BoolVar(&o.dryRun, "dry-run", false, "simulate an install")
f.BoolVar(&o.disableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&o.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
f.StringVar(&o.nameTemplate, "name-template", "", "specify template used to name the release")
f.Int64Var(&o.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&o.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&o.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.BoolVar(&o.depUp, "dep-up", false, "run helm dependency update before installing the chart")
o.valuesOptions.addFlags(f)
o.chartPathOptions.addFlags(f)
return cmd return cmd
} }
// nameAndChart returns the name of the release and the chart that should be used. func addInstallFlags(f *pflag.FlagSet, client *action.Install) {
// f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
// This will read the flags and handle name generation if necessary. f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
func (o *installOptions) nameAndChart(args []string) (string, string, error) { f.BoolVar(&client.Replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
flagsNotSet := func() error { f.Int64Var(&client.Timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
if o.generateName { f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
return errors.New("cannot set --generate-name and also specify a name") 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")
if o.nameTemplate != "" { f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
return errors.New("cannot set --name-template and also specify a name") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart")
} addValueOptionsFlags(f, &client.ValueOptions)
return nil addChartPathOptionsFlags(f, &client.ChartPathOptions)
} }
if len(args) == 2 {
return args[0], args[1], flagsNotSet() func addValueOptionsFlags(f *pflag.FlagSet, v *action.ValueOptions) {
} f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL(can specify multiple)")
f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
if o.nameTemplate != "" { f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
newName, err := templateName(o.nameTemplate) }
return newName, args[0], err
} func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.StringVar(&c.Version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed")
if !o.generateName { f.BoolVar(&c.Verify, "verify", false, "verify the package before installing it")
return "", args[0], errors.New("must either provide a name or specify --generate-name") f.StringVar(&c.Keyring, "keyring", defaultKeyring(), "location of public keys used for verification")
} f.StringVar(&c.RepoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&c.Username, "username", "", "chart repository username where to locate the requested chart")
base := filepath.Base(args[0]) f.StringVar(&c.Password, "password", "", "chart repository password where to locate the requested chart")
if base == "." || base == "" { f.StringVar(&c.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
base = "chart" f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file")
} f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
newName := fmt.Sprintf("%s-%d", base, time.Now().Unix()) }
return newName, args[0], nil func runInstall(args []string, client *action.Install, 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")
client.Version = ">0.0.0-0"
} }
func (o *installOptions) run(out io.Writer) error { name, chart, err := client.NameAndChart(args)
debug("CHART PATH: %s\n", o.chartPath)
rawVals, err := o.mergedValues()
if err != nil { if err != nil {
return err return nil, err
} }
client.ReleaseName = name
// If template is specified, try to run the template. cp, err := client.ChartPathOptions.LocateChart(chart, settings)
if o.nameTemplate != "" {
o.name, err = templateName(o.nameTemplate)
if err != nil { if err != nil {
return err return nil, err
} }
// Print the final name so the user knows what the final name of the release is.
fmt.Fprintf(out, "FINAL NAME: %s\n", o.name) debug("CHART PATH: %s\n", cp)
if err := client.ValueOptions.MergeValues(settings); err != nil {
return nil, err
} }
// Check chart dependencies to make sure all are present in /charts // Check chart dependencies to make sure all are present in /charts
chartRequested, err := loader.Load(o.chartPath) chartRequested, err := loader.Load(cp)
if err != nil { if err != nil {
return err return nil, err
} }
validInstallableChart, err := chartutil.IsChartInstallable(chartRequested) validInstallableChart, err := chartutil.IsChartInstallable(chartRequested)
if !validInstallableChart { if !validInstallableChart {
return err return nil, err
} }
if req := chartRequested.Metadata.Dependencies; req != nil { if req := chartRequested.Metadata.Dependencies; req != nil {
// If checkDependencies returns an error, we have unfulfilled dependencies. // If CheckDependencies returns an error, we have unfulfilled dependencies.
// As of Helm 2.4.0, this is treated as a stopping condition: // As of Helm 2.4.0, this is treated as a stopping condition:
// https://github.com/helm/helm/issues/2209 // https://github.com/helm/helm/issues/2209
if err := checkDependencies(chartRequested, req); err != nil { if err := action.CheckDependencies(chartRequested, req); err != nil {
if o.depUp { if client.DependencyUpdate {
man := &downloader.Manager{ man := &downloader.Manager{
Out: out, Out: out,
ChartPath: o.chartPath, ChartPath: cp,
HelmHome: settings.Home, HelmHome: settings.Home,
Keyring: o.keyring, Keyring: client.ChartPathOptions.Keyring,
SkipUpdate: false, SkipUpdate: false,
Getters: getter.All(settings), Getters: getter.All(settings),
} }
if err := man.Update(); err != nil { if err := man.Update(); err != nil {
return err return nil, err
} }
} else { } else {
return err return nil, err
} }
}
}
inst := action.NewInstall(o.cfg)
inst.DryRun = o.dryRun
inst.DisableHooks = o.disableHooks
inst.Replace = o.replace
inst.Wait = o.wait
inst.Devel = o.devel
inst.Timeout = o.timeout
inst.Namespace = getNamespace()
inst.ReleaseName = o.name
rel, err := inst.Run(chartRequested, rawVals)
if err != nil {
return err
}
o.printRelease(out, rel)
return nil
}
// printRelease prints info about a release
func (o *installOptions) printRelease(out io.Writer, rel *release.Release) {
if rel == nil {
return
}
fmt.Fprintf(out, "NAME: %s\n", rel.Name)
if settings.Debug {
printRelease(out, rel)
}
if !rel.Info.LastDeployed.IsZero() {
fmt.Fprintf(out, "LAST DEPLOYED: %s\n", rel.Info.LastDeployed)
}
fmt.Fprintf(out, "NAMESPACE: %s\n", rel.Namespace)
fmt.Fprintf(out, "STATUS: %s\n", rel.Info.Status.String())
fmt.Fprintf(out, "\n")
if len(rel.Info.Resources) > 0 {
re := regexp.MustCompile(" +")
w := tabwriter.NewWriter(out, 0, 0, 2, ' ', tabwriter.TabIndent)
fmt.Fprintf(w, "RESOURCES:\n%s\n", re.ReplaceAllString(rel.Info.Resources, "\t"))
w.Flush()
}
if rel.Info.LastTestSuiteRun != nil {
lastRun := rel.Info.LastTestSuiteRun
fmt.Fprintf(out, "TEST SUITE:\n%s\n%s\n\n%s\n",
fmt.Sprintf("Last Started: %s", lastRun.StartedAt),
fmt.Sprintf("Last Completed: %s", lastRun.CompletedAt),
formatTestResults(lastRun.Results))
}
if len(rel.Info.Notes) > 0 {
fmt.Fprintf(out, "NOTES:\n%s\n", rel.Info.Notes)
} }
} }
// Merges source and destination map, preferring values from the source map client.Namespace = getNamespace()
func mergeValues(dest, src map[string]interface{}) map[string]interface{} { return client.Run(chartRequested)
for k, v := range src {
// If the key doesn't exist already, then just set the key to that value
if _, exists := dest[k]; !exists {
dest[k] = v
continue
}
nextMap, ok := v.(map[string]interface{})
// If it isn't another map, overwrite the value
if !ok {
dest[k] = v
continue
}
// Edge case: If the key exists in the destination, but isn't a map
destMap, isMap := dest[k].(map[string]interface{})
// If the source map has a map for this key, prefer it
if !isMap {
dest[k] = v
continue
}
// If we got to this point, it is a map in both, so merge them
dest[k] = mergeValues(destMap, nextMap)
}
return dest
}
func templateName(nameTemplate string) (string, error) {
t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate)
if err != nil {
return "", err
}
var b bytes.Buffer
err = t.Execute(&b, nil)
return b.String(), err
}
func checkDependencies(ch *chart.Chart, reqs []*chart.Dependency) error {
var missing []string
OUTER:
for _, r := range reqs {
for _, d := range ch.Dependencies() {
if d.Name() == r.Name {
continue OUTER
}
}
missing = append(missing, r.Name)
}
if len(missing) > 0 {
return errors.Errorf("found in Chart.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", "))
}
return nil
} }

@ -17,8 +17,6 @@ limitations under the License.
package main package main
import ( import (
"reflect"
"regexp"
"testing" "testing"
) )
@ -142,133 +140,3 @@ func TestInstall(t *testing.T) {
runTestActionCmd(t, tests) runTestActionCmd(t, tests)
} }
type nameTemplateTestCase struct {
tpl string
expected string
expectedErrorStr string
}
func TestNameTemplate(t *testing.T) {
testCases := []nameTemplateTestCase{
// Just a straight up nop please
{
tpl: "foobar",
expected: "foobar",
expectedErrorStr: "",
},
// Random numbers at the end for fun & profit
{
tpl: "foobar-{{randNumeric 6}}",
expected: "foobar-[0-9]{6}$",
expectedErrorStr: "",
},
// Random numbers in the middle for fun & profit
{
tpl: "foobar-{{randNumeric 4}}-baz",
expected: "foobar-[0-9]{4}-baz$",
expectedErrorStr: "",
},
// No such function
{
tpl: "foobar-{{randInt}}",
expected: "",
expectedErrorStr: "function \"randInt\" not defined",
},
// Invalid template
{
tpl: "foobar-{{",
expected: "",
expectedErrorStr: "unexpected unclosed action",
},
}
for _, tc := range testCases {
n, err := templateName(tc.tpl)
if err != nil {
if tc.expectedErrorStr == "" {
t.Errorf("Was not expecting error, but got: %v", err)
continue
}
re, compErr := regexp.Compile(tc.expectedErrorStr)
if compErr != nil {
t.Errorf("Expected error string failed to compile: %v", compErr)
continue
}
if !re.MatchString(err.Error()) {
t.Errorf("Error didn't match for %s expected %s but got %v", tc.tpl, tc.expectedErrorStr, err)
continue
}
}
if err == nil && tc.expectedErrorStr != "" {
t.Errorf("Was expecting error %s but didn't get an error back", tc.expectedErrorStr)
}
if tc.expected != "" {
re, err := regexp.Compile(tc.expected)
if err != nil {
t.Errorf("Expected string failed to compile: %v", err)
continue
}
if !re.MatchString(n) {
t.Errorf("Returned name didn't match for %s expected %s but got %s", tc.tpl, tc.expected, n)
}
}
}
}
func TestMergeValues(t *testing.T) {
nestedMap := map[string]interface{}{
"foo": "bar",
"baz": map[string]string{
"cool": "stuff",
},
}
anotherNestedMap := map[string]interface{}{
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
flatMap := map[string]interface{}{
"foo": "bar",
"baz": "stuff",
}
anotherFlatMap := map[string]interface{}{
"testing": "fun",
}
testMap := mergeValues(flatMap, nestedMap)
equal := reflect.DeepEqual(testMap, nestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap)
}
testMap = mergeValues(nestedMap, flatMap)
equal = reflect.DeepEqual(testMap, flatMap)
if !equal {
t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap)
}
testMap = mergeValues(nestedMap, anotherNestedMap)
equal = reflect.DeepEqual(testMap, anotherNestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap)
}
testMap = mergeValues(anotherFlatMap, anotherNestedMap)
expectedMap := map[string]interface{}{
"testing": "fun",
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
equal = reflect.DeepEqual(testMap, expectedMap)
if !equal {
t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap)
}
}

@ -19,19 +19,12 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os"
"path/filepath"
"strings" "strings"
"github.com/ghodss/yaml"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/lint"
"k8s.io/helm/pkg/lint/support"
"k8s.io/helm/pkg/strvals"
) )
var longLintHelp = ` var longLintHelp = `
@ -43,159 +36,43 @@ it will emit [ERROR] messages. If it encounters issues that break with conventio
or recommendation, it will emit [WARNING] messages. or recommendation, it will emit [WARNING] messages.
` `
type lintOptions struct {
strict bool
paths []string
valuesOptions
}
func newLintCmd(out io.Writer) *cobra.Command { func newLintCmd(out io.Writer) *cobra.Command {
o := &lintOptions{paths: []string{"."}} client := action.NewLint()
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "lint PATH", Use: "lint PATH",
Short: "examines a chart for possible issues", Short: "examines a chart for possible issues",
Long: longLintHelp, Long: longLintHelp,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
paths := []string{"."}
if len(args) > 0 { if len(args) > 0 {
o.paths = args paths = args
} }
return o.run(out) client.Namespace = getNamespace()
}, if err := client.ValueOptions.MergeValues(settings); err != nil {
}
fs := cmd.Flags()
fs.BoolVar(&o.strict, "strict", false, "fail on lint warnings")
o.valuesOptions.addFlags(fs)
return cmd
}
var errLintNoChart = errors.New("no chart found for linting (missing Chart.yaml)")
func (o *lintOptions) run(out io.Writer) error {
var lowestTolerance int
if o.strict {
lowestTolerance = support.WarningSev
} else {
lowestTolerance = support.ErrorSev
}
// Get the raw values
rvals, err := o.vals()
if err != nil {
return err return err
} }
result := client.Run(paths)
var total int var message strings.Builder
var failures int fmt.Fprintf(&message, "%d chart(s) linted, %d chart(s) failed\n", result.TotalChartsLinted, len(result.Errors))
for _, path := range o.paths { for _, err := range result.Errors {
if linter, err := lintChart(path, rvals, getNamespace(), o.strict); err != nil { fmt.Fprintf(&message, "\t%s\n", err)
fmt.Println("==> Skipping", path)
fmt.Println(err)
if err == errLintNoChart {
failures = failures + 1
}
} else {
fmt.Println("==> Linting", path)
if len(linter.Messages) == 0 {
fmt.Println("Lint OK")
}
for _, msg := range linter.Messages {
fmt.Println(msg)
}
total = total + 1
if linter.HighestSeverity >= lowestTolerance {
failures = failures + 1
}
} }
fmt.Println("") for _, msg := range result.Messages {
fmt.Fprintf(&message, "\t%s\n", msg)
} }
msg := fmt.Sprintf("%d chart(s) linted", total) if len(result.Errors) > 0 {
if failures > 0 { return errors.New(message.String())
return errors.Errorf("%s, %d chart(s) failed", msg, failures)
} }
fmt.Fprintf(out, message.String())
fmt.Fprintf(out, "%s, no failures\n", msg)
return nil return nil
},
} }
func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) { f := cmd.Flags()
var chartPath string f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings")
linter := support.Linter{} addValueOptionsFlags(f, &client.ValueOptions)
if strings.HasSuffix(path, ".tgz") {
tempDir, err := ioutil.TempDir("", "helm-lint")
if err != nil {
return linter, err
}
defer os.RemoveAll(tempDir)
file, err := os.Open(path)
if err != nil {
return linter, err
}
defer file.Close()
if err = chartutil.Expand(tempDir, file); err != nil {
return linter, err
}
lastHyphenIndex := strings.LastIndex(filepath.Base(path), "-")
if lastHyphenIndex <= 0 {
return linter, errors.Errorf("unable to parse chart archive %q, missing '-'", filepath.Base(path))
}
base := filepath.Base(path)[:lastHyphenIndex]
chartPath = filepath.Join(tempDir, base)
} else {
chartPath = path
}
// Guard: Error out of this is not a chart.
if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil {
return linter, errLintNoChart
}
return lint.All(chartPath, vals, namespace, strict), nil
}
func (o *lintOptions) vals() (map[string]interface{}, error) {
base := map[string]interface{}{}
// User specified a values files via -f/--values
for _, filePath := range o.valueFiles {
currentMap := map[string]interface{}{}
bytes, err := ioutil.ReadFile(filePath)
if err != nil {
return base, err
}
if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
return base, errors.Wrapf(err, "failed to parse %s", filePath)
}
// Merge with the previous map
base = mergeValues(base, currentMap)
}
// User specified a value via --set
for _, value := range o.values {
if err := strvals.ParseInto(value, base); err != nil {
return base, errors.Wrap(err, "failed parsing --set data")
}
}
// User specified a value via --set-string
for _, value := range o.stringValues {
if err := strvals.ParseIntoString(value, base); err != nil {
return base, errors.Wrap(err, "failed parsing --set-string data")
}
}
return base, nil return cmd
} }

@ -19,14 +19,11 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"strings"
"github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/action" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/hapi/release"
) )
var listHelp = ` var listHelp = `
@ -39,11 +36,11 @@ By default, it lists only releases that are deployed or failed. Flags like
By default, items are sorted alphabetically. Use the '-d' flag to sort by By default, items are sorted alphabetically. Use the '-d' flag to sort by
release date. release date.
If an argument is provided, it will be treated as a filter. Filters are If the --filter flag is provided, it will be treated as a filter. Filters are
regular expressions (Perl compatible) that are applied to the list of releases. regular expressions (Perl compatible) that are applied to the list of releases.
Only items that match the filter will be returned. Only items that match the filter will be returned.
$ helm list 'ara[a-z]+' $ helm list --filter 'ara[a-z]+'
NAME UPDATED CHART NAME UPDATED CHART
maudlin-arachnid Mon May 9 16:07:08 2016 alpine-0.1.0 maudlin-arachnid Mon May 9 16:07:08 2016 alpine-0.1.0
@ -56,143 +53,51 @@ server's default, which may be much higher than 256. Pairing the '--max'
flag with the '--offset' flag allows you to page through results. flag with the '--offset' flag allows you to page through results.
` `
type listOptions struct { func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
// flags client := action.NewList(cfg)
all bool // --all
allNamespaces bool // --all-namespaces
byDate bool // --date
colWidth uint // --col-width
uninstalled bool // --uninstalled
uninstalling bool // --uninstalling
deployed bool // --deployed
failed bool // --failed
limit int // --max
offset int // --offset
pending bool // --pending
short bool // --short
sortDesc bool // --reverse
superseded bool // --superseded
filter string
}
func newListCmd(actionConfig *action.Configuration, out io.Writer) *cobra.Command {
o := &listOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "list [FILTER]", Use: "list",
Short: "list releases", Short: "list releases",
Long: listHelp, Long: listHelp,
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Args: require.MaximumNArgs(1), Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 { if client.AllNamespaces {
o.filter = strings.Join(args, " ") client.SetConfiguration(newActionConfig(true))
}
if o.allNamespaces {
actionConfig = newActionConfig(true)
} }
client.All = client.Limit == -1
client.SetStateMask()
lister := action.NewList(actionConfig) results, err := client.Run()
lister.All = o.limit == -1
lister.AllNamespaces = o.allNamespaces
lister.Limit = o.limit
lister.Offset = o.offset
lister.Filter = o.filter
// Set StateMask if client.Short {
lister.StateMask = o.setStateMask()
// Set sorter
lister.Sort = action.ByNameAsc
if o.sortDesc {
lister.Sort = action.ByNameDesc
}
if o.byDate {
lister.Sort = action.ByDate
}
results, err := lister.Run()
if o.short {
for _, res := range results { for _, res := range results {
fmt.Fprintln(out, res.Name) fmt.Fprintln(out, res.Name)
} }
return err return err
} }
fmt.Fprintln(out, formatList(results, 90)) fmt.Fprintln(out, action.FormatList(results))
return err return err
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVarP(&o.short, "short", "q", false, "output short (quiet) listing format") f.BoolVarP(&client.Short, "short", "q", false, "output short (quiet) listing format")
f.BoolVarP(&o.byDate, "date", "d", false, "sort by release date") f.BoolVarP(&client.ByDate, "date", "d", false, "sort by release date")
f.BoolVarP(&o.sortDesc, "reverse", "r", false, "reverse the sort order") f.BoolVarP(&client.SortDesc, "reverse", "r", false, "reverse the sort order")
f.IntVarP(&o.limit, "max", "m", 256, "maximum number of releases to fetch") f.BoolVarP(&client.All, "all", "a", false, "show all releases, not just the ones marked deployed")
f.IntVarP(&o.offset, "offset", "o", 0, "next release name in the list, used to offset from start value") f.BoolVar(&client.Uninstalled, "uninstalled", false, "show uninstalled releases")
f.BoolVarP(&o.all, "all", "a", false, "show all releases, not just the ones marked deployed") f.BoolVar(&client.Superseded, "superseded", false, "show superseded releases")
f.BoolVar(&o.uninstalled, "uninstalled", false, "show uninstalled releases") f.BoolVar(&client.Uninstalling, "uninstalling", false, "show releases that are currently being uninstalled")
f.BoolVar(&o.superseded, "superseded", false, "show superseded releases") f.BoolVar(&client.Deployed, "deployed", false, "show deployed releases. If no other is specified, this will be automatically enabled")
f.BoolVar(&o.uninstalling, "uninstalling", false, "show releases that are currently being uninstalled") f.BoolVar(&client.Failed, "failed", false, "show failed releases")
f.BoolVar(&o.deployed, "deployed", false, "show deployed releases. If no other is specified, this will be automatically enabled") f.BoolVar(&client.Pending, "pending", false, "show pending releases")
f.BoolVar(&o.failed, "failed", false, "show failed releases") f.BoolVar(&client.AllNamespaces, "all-namespaces", false, "list releases across all namespaces")
f.BoolVar(&o.pending, "pending", false, "show pending releases") f.IntVarP(&client.Limit, "max", "m", 256, "maximum number of releases to fetch")
f.UintVar(&o.colWidth, "col-width", 60, "specifies the max column width of output") f.IntVarP(&client.Offset, "offset", "o", 0, "next release name in the list, used to offset from start value")
f.BoolVar(&o.allNamespaces, "all-namespaces", false, "list releases across all namespaces") f.StringVarP(&client.Filter, "filter", "f", "", "a regular expression (Perl compatible). Any releases that match the expression will be included in the results")
return cmd return cmd
} }
// setStateMask calculates the state mask based on parameters.
func (o *listOptions) setStateMask() action.ListStates {
if o.all {
return action.ListAll
}
state := action.ListStates(0)
if o.deployed {
state |= action.ListDeployed
}
if o.uninstalled {
state |= action.ListUninstalled
}
if o.uninstalling {
state |= action.ListUninstalling
}
if o.pending {
state |= action.ListPendingInstall | action.ListPendingRollback | action.ListPendingUpgrade
}
if o.failed {
state |= action.ListFailed
}
// Apply a default
if state == 0 {
return action.ListDeployed | action.ListFailed
}
return state
}
func formatList(rels []*release.Release, colWidth uint) string {
table := uitable.New()
table.MaxColWidth = colWidth
table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "NAMESPACE")
for _, r := range rels {
md := r.Chart.Metadata
c := fmt.Sprintf("%s-%s", md.Name, md.Version)
t := "-"
if tspb := r.Info.LastDeployed; !tspb.IsZero() {
t = tspb.String()
}
s := r.Info.Status.String()
v := r.Version
n := r.Namespace
table.AddRow(r.Name, v, t, s, c, n)
}
return table.String()
}

@ -1,225 +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 "k8s.io/helm/cmd/helm"
import (
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
"github.com/spf13/pflag"
"k8s.io/helm/pkg/downloader"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/strvals"
)
// -----------------------------------------------------------------------------
// Values Options
type valuesOptions struct {
valueFiles []string // --values
values []string // --set
stringValues []string // --set-string
}
func (o *valuesOptions) addFlags(fs *pflag.FlagSet) {
fs.StringSliceVarP(&o.valueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL(can specify multiple)")
fs.StringArrayVar(&o.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
fs.StringArrayVar(&o.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
}
// mergeValues merges values from files specified via -f/--values and
// directly via --set or --set-string, marshaling them to YAML
func (o *valuesOptions) mergedValues() (map[string]interface{}, error) {
base := map[string]interface{}{}
// User specified a values files via -f/--values
for _, filePath := range o.valueFiles {
currentMap := map[string]interface{}{}
bytes, err := readFile(filePath)
if err != nil {
return base, err
}
if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
return base, errors.Wrapf(err, "failed to parse %s", filePath)
}
// Merge with the previous map
base = mergeValues(base, currentMap)
}
// User specified a value via --set
for _, value := range o.values {
if err := strvals.ParseInto(value, base); err != nil {
return base, errors.Wrap(err, "failed parsing --set data")
}
}
// User specified a value via --set-string
for _, value := range o.stringValues {
if err := strvals.ParseIntoString(value, base); err != nil {
return base, errors.Wrap(err, "failed parsing --set-string data")
}
}
return base, nil
}
// readFile load a file from stdin, the local directory, or a remote file with a url.
func readFile(filePath string) ([]byte, error) {
if strings.TrimSpace(filePath) == "-" {
return ioutil.ReadAll(os.Stdin)
}
u, _ := url.Parse(filePath)
p := getter.All(settings)
// FIXME: maybe someone handle other protocols like ftp.
getterConstructor, err := p.ByScheme(u.Scheme)
if err != nil {
return ioutil.ReadFile(filePath)
}
getter, err := getterConstructor(filePath, "", "", "")
if err != nil {
return []byte{}, err
}
data, err := getter.Get(filePath)
return data.Bytes(), err
}
// -----------------------------------------------------------------------------
// Chart Path Options
type chartPathOptions struct {
caFile string // --ca-file
certFile string // --cert-file
keyFile string // --key-file
keyring string // --keyring
password string // --password
repoURL string // --repo
username string // --username
verify bool // --verify
version string // --version
}
// defaultKeyring returns the expanded path to the default keyring.
func defaultKeyring() string {
if v, ok := os.LookupEnv("GNUPGHOME"); ok {
return filepath.Join(v, "pubring.gpg")
}
return os.ExpandEnv("$HOME/.gnupg/pubring.gpg")
}
func (o *chartPathOptions) addFlags(fs *pflag.FlagSet) {
fs.StringVar(&o.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed")
fs.BoolVar(&o.verify, "verify", false, "verify the package before installing it")
fs.StringVar(&o.keyring, "keyring", defaultKeyring(), "location of public keys used for verification")
fs.StringVar(&o.repoURL, "repo", "", "chart repository url where to locate the requested chart")
fs.StringVar(&o.username, "username", "", "chart repository username where to locate the requested chart")
fs.StringVar(&o.password, "password", "", "chart repository password where to locate the requested chart")
fs.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
fs.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
fs.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
}
func (o *chartPathOptions) locateChart(name string) (string, error) {
return locateChartPath(o.repoURL, o.username, o.password, name, o.version, o.keyring, o.certFile, o.keyFile, o.caFile, o.verify)
}
// locateChartPath looks for a chart directory in known places, and returns either the full path or an error.
//
// This does not ensure that the chart is well-formed; only that the requested filename 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
//
// If 'verify' is true, this will attempt to also verify the chart.
func locateChartPath(repoURL, username, password, name, version, keyring,
certFile, keyFile, caFile string, verify bool) (string, error) {
name = strings.TrimSpace(name)
version = strings.TrimSpace(version)
if _, err := os.Stat(name); err == nil {
abs, err := filepath.Abs(name)
if err != nil {
return abs, err
}
if verify {
if _, err := downloader.VerifyChart(abs, keyring); err != nil {
return "", err
}
}
return abs, nil
}
if filepath.IsAbs(name) || strings.HasPrefix(name, ".") {
return name, 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)
}
dl := downloader.ChartDownloader{
HelmHome: settings.Home,
Out: os.Stdout,
Keyring: keyring,
Getters: getter.All(settings),
Username: username,
Password: password,
}
if verify {
dl.Verify = downloader.VerifyAlways
}
if repoURL != "" {
chartURL, err := repo.FindChartInAuthRepoURL(repoURL, username, password, name, version,
certFile, keyFile, 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)
}
filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive())
if err == nil {
lname, err := filepath.Abs(filename)
if err != nil {
return filename, err
}
debug("Fetched %s to %s\n", name, filename)
return lname, nil
} else if settings.Debug {
return filename, err
}
return filename, errors.Errorf("failed to download %q (hint: running `helm repo update` may help)", name)
}

@ -20,22 +20,15 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os"
"path/filepath" "path/filepath"
"syscall"
"github.com/Masterminds/semver" "k8s.io/helm/pkg/action"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/downloader" "k8s.io/helm/pkg/downloader"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/provenance"
) )
const packageDesc = ` const packageDesc = `
@ -49,77 +42,41 @@ Chart.yaml file, and (if found) build the current directory into a chart.
Versioned chart archives are used by Helm package repositories. Versioned chart archives are used by Helm package repositories.
` `
type packageOptions struct {
appVersion string // --app-version
dependencyUpdate bool // --dependency-update
destination string // --destination
key string // --key
keyring string // --keyring
sign bool // --sign
version string // --version
valuesOptions
path string
home helmpath.Home
}
func newPackageCmd(out io.Writer) *cobra.Command { func newPackageCmd(out io.Writer) *cobra.Command {
o := &packageOptions{} client := action.NewPackage()
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "package [CHART_PATH] [...]", Use: "package [CHART_PATH] [...]",
Short: "package a chart directory into a chart archive", Short: "package a chart directory into a chart archive",
Long: packageDesc, Long: packageDesc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.home = settings.Home
if len(args) == 0 { if len(args) == 0 {
return errors.Errorf("need at least one argument, the path to the chart") return errors.Errorf("need at least one argument, the path to the chart")
} }
if o.sign { if client.Sign {
if o.key == "" { if client.Key == "" {
return errors.New("--key is required for signing a package") return errors.New("--key is required for signing a package")
} }
if o.keyring == "" { if client.Keyring == "" {
return errors.New("--keyring is required for signing a package") return errors.New("--keyring is required for signing a package")
} }
} }
for i := 0; i < len(args); i++ { if err := client.ValueOptions.MergeValues(settings); err != nil {
o.path = args[i]
if err := o.run(out); err != nil {
return err return err
} }
}
return nil
},
}
f := cmd.Flags() for i := 0; i < len(args); i++ {
f.BoolVar(&o.sign, "sign", false, "use a PGP private key to sign this package") path, err := filepath.Abs(args[i])
f.StringVar(&o.key, "key", "", "name of the key to use when signing. Used if --sign is true")
f.StringVar(&o.keyring, "keyring", defaultKeyring(), "location of a public keyring")
f.StringVar(&o.version, "version", "", "set the version on the chart to this semver version")
f.StringVar(&o.appVersion, "app-version", "", "set the appVersion on the chart to this version")
f.StringVarP(&o.destination, "destination", "d", ".", "location to write the chart.")
f.BoolVarP(&o.dependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`)
o.valuesOptions.addFlags(f)
return cmd
}
func (o *packageOptions) run(out io.Writer) error {
path, err := filepath.Abs(o.path)
if err != nil { if err != nil {
return err return err
} }
if o.dependencyUpdate { if client.DependencyUpdate {
downloadManager := &downloader.Manager{ downloadManager := &downloader.Manager{
Out: out, Out: ioutil.Discard,
ChartPath: path, ChartPath: path,
HelmHome: settings.Home, HelmHome: settings.Home,
Keyring: o.keyring, Keyring: client.Keyring,
Getters: getter.All(settings), Getters: getter.All(settings),
Debug: settings.Debug, Debug: settings.Debug,
} }
@ -128,107 +85,25 @@ func (o *packageOptions) run(out io.Writer) error {
return err return err
} }
} }
p, err := client.Run(path)
ch, err := loader.LoadDir(path)
if err != nil {
return err
}
validChartType, err := chartutil.IsValidChartType(ch)
if !validChartType {
return err
}
overrideVals, err := o.mergedValues()
if err != nil {
return err
}
combinedVals, err := chartutil.CoalesceValues(ch, overrideVals)
if err != nil {
return err
}
ch.Values = combinedVals
// If version is set, modify the version.
if len(o.version) != 0 {
if err := setVersion(ch, o.version); err != nil {
return err
}
debug("Setting version to %s", o.version)
}
if o.appVersion != "" {
ch.Metadata.AppVersion = o.appVersion
debug("Setting appVersion to %s", o.appVersion)
}
if reqs := ch.Metadata.Dependencies; reqs != nil {
if err := checkDependencies(ch, reqs); err != nil {
return err
}
}
var dest string
if o.destination == "." {
// Save to the current working directory.
dest, err = os.Getwd()
if err != nil {
return err
}
} else {
// Otherwise save to set destination
dest = o.destination
}
name, err := chartutil.Save(ch, dest)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to save")
}
fmt.Fprintf(out, "Successfully packaged chart and saved it to: %s\n", name)
if o.sign {
err = o.clearsign(name)
}
return err return err
} }
fmt.Fprintf(out, "Successfully packaged chart and saved it to: %s\n", p)
func setVersion(ch *chart.Chart, ver string) error {
// Verify that version is a Version, and error out if it is not.
if _, err := semver.NewVersion(ver); err != nil {
return err
} }
// Set the version field on the chart.
ch.Metadata.Version = ver
return nil return nil
},
} }
func (o *packageOptions) clearsign(filename string) error { f := cmd.Flags()
// Load keyring f.BoolVar(&client.Sign, "sign", false, "use a PGP private key to sign this package")
signer, err := provenance.NewFromKeyring(o.keyring, o.key) f.StringVar(&client.Key, "key", "", "name of the key to use when signing. Used if --sign is true")
if err != nil { f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "location of a public keyring")
return err 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.")
if err := signer.DecryptKey(promptUser); err != nil { f.BoolVarP(&client.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`)
return err addValueOptionsFlags(f, &client.ValueOptions)
}
sig, err := signer.ClearSign(filename)
if err != nil {
return err
}
debug(sig)
return ioutil.WriteFile(filename+".prov", []byte(sig), 0755)
}
// promptUser implements provenance.PassphraseFetcher return cmd
func promptUser(name string) ([]byte, error) {
fmt.Printf("Password for key %q > ", name)
pw, err := terminal.ReadPassword(int(syscall.Stdin))
fmt.Println()
return pw, err
} }

@ -31,30 +31,9 @@ import (
"k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
) )
func TestSetVersion(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{
Name: "prow",
Version: "0.0.1",
},
}
expect := "1.2.3-beta.5"
if err := setVersion(c, expect); err != nil {
t.Fatal(err)
}
if c.Metadata.Version != expect {
t.Errorf("Expected %q, got %q", expect, c.Metadata.Version)
}
if err := setVersion(c, "monkeyface"); err == nil {
t.Error("Expected bogus version to return an error.")
}
}
func TestPackage(t *testing.T) { func TestPackage(t *testing.T) {
statExe := "stat" statExe := "stat"
statFileMsg := "no such file or directory" statFileMsg := "no such file or directory"

@ -22,7 +22,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/plugin" "k8s.io/helm/pkg/plugin"
"k8s.io/helm/pkg/plugin/installer" "k8s.io/helm/pkg/plugin/installer"
) )

@ -22,7 +22,7 @@ import (
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
) )
type pluginListOptions struct { type pluginListOptions struct {

@ -24,7 +24,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/plugin" "k8s.io/helm/pkg/plugin"
) )

@ -25,7 +25,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/plugin" "k8s.io/helm/pkg/plugin"
) )

@ -24,7 +24,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/plugin" "k8s.io/helm/pkg/plugin"
"k8s.io/helm/pkg/plugin/installer" "k8s.io/helm/pkg/plugin/installer"
) )

@ -24,7 +24,7 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/release"
) )
var printReleaseTemplate = `REVISION: {{.Release.Version}} var printReleaseTemplate = `REVISION: {{.Release.Version}}

@ -19,18 +19,11 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/downloader"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/repo"
) )
const pullDesc = ` const pullDesc = `
@ -48,20 +41,8 @@ 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. result in an error, and the chart will not be saved locally.
` `
type pullOptions struct {
destdir string // --destination
devel bool // --devel
untar bool // --untar
untardir string // --untardir
verifyLater bool // --prov
chartRef string
chartPathOptions
}
func newPullCmd(out io.Writer) *cobra.Command { func newPullCmd(out io.Writer) *cobra.Command {
o := &pullOptions{} client := action.NewPull()
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "pull [chart URL | repo/chartname] [...]", Use: "pull [chart URL | repo/chartname] [...]",
@ -70,95 +51,30 @@ func newPullCmd(out io.Writer) *cobra.Command {
Long: pullDesc, Long: pullDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if o.version == "" && o.devel { client.Settings = settings
if client.Version == "" && client.Devel {
debug("setting version to >0.0.0-0") debug("setting version to >0.0.0-0")
o.version = ">0.0.0-0" client.Version = ">0.0.0-0"
} }
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
o.chartRef = args[i] output, err := client.Run(args[i])
if err := o.run(out); err != nil { if err != nil {
return err return err
} }
fmt.Fprint(out, output)
} }
return nil return nil
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&o.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") 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(&o.untar, "untar", false, "if set to true, will untar the chart after downloading it") f.BoolVar(&client.Untar, "untar", false, "if set to true, will untar the chart after downloading it")
f.BoolVar(&o.verifyLater, "prov", false, "fetch the provenance file, but don't perform verification") f.BoolVar(&client.VerifyLater, "prov", false, "fetch the provenance file, but don't perform verification")
f.StringVar(&o.untardir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded") f.StringVar(&client.UntarDir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded")
f.StringVarP(&o.destdir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this") f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this")
addChartPathOptionsFlags(f, &client.ChartPathOptions)
o.chartPathOptions.addFlags(f)
return cmd return cmd
} }
func (o *pullOptions) run(out io.Writer) error {
c := downloader.ChartDownloader{
HelmHome: settings.Home,
Out: out,
Keyring: o.keyring,
Verify: downloader.VerifyNever,
Getters: getter.All(settings),
Username: o.username,
Password: o.password,
}
if o.verify {
c.Verify = downloader.VerifyAlways
} else if o.verifyLater {
c.Verify = downloader.VerifyLater
}
// If untar is set, we fetch to a tempdir, then untar and copy after
// verification.
dest := o.destdir
if o.untar {
var err error
dest, err = ioutil.TempDir("", "helm-")
if err != nil {
return errors.Wrap(err, "failed to untar")
}
defer os.RemoveAll(dest)
}
if o.repoURL != "" {
chartURL, err := repo.FindChartInAuthRepoURL(o.repoURL, o.username, o.password, o.chartRef, o.version, o.certFile, o.keyFile, o.caFile, getter.All(settings))
if err != nil {
return err
}
o.chartRef = chartURL
}
saved, v, err := c.DownloadTo(o.chartRef, o.version, dest)
if err != nil {
return err
}
if o.verify {
fmt.Fprintf(out, "Verification: %v\n", v)
}
// After verification, untar the chart into the requested directory.
if o.untar {
ud := o.untardir
if !filepath.IsAbs(ud) {
ud = filepath.Join(o.destdir, ud)
}
if fi, err := os.Stat(ud); err != nil {
if err := os.MkdirAll(ud, 0755); err != nil {
return errors.Wrap(err, "failed to untar (mkdir)")
}
} else if !fi.IsDir() {
return errors.Errorf("failed to untar: %s is not a directory", ud)
}
return chartutil.ExpandFile(ud, saved)
}
return nil
}

@ -130,7 +130,7 @@ func TestPullCmd(t *testing.T) {
os.Mkdir(outdir, 0755) os.Mkdir(outdir, 0755)
cmd := strings.Join(append(tt.args, "-d", "'"+outdir+"'", "--home", "'"+hh.String()+"'"), " ") cmd := strings.Join(append(tt.args, "-d", "'"+outdir+"'", "--home", "'"+hh.String()+"'"), " ")
out, err := executeCommand(nil, "fetch "+cmd) _, out, err := executeActionCommand("fetch " + cmd)
if err != nil { if err != nil {
if tt.wantError { if tt.wantError {
continue continue

@ -24,8 +24,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/release"
) )
const releaseTestDesc = ` const releaseTestDesc = `
@ -35,15 +35,8 @@ The argument this command takes is the name of a deployed release.
The tests to be run are defined in the chart that was installed. The tests to be run are defined in the chart that was installed.
` `
type releaseTestOptions struct { func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
name string client := action.NewReleaseTesting(cfg)
client helm.Interface
timeout int64
cleanup bool
}
func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
o := &releaseTestOptions{client: c}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "test [RELEASE]", Use: "test [RELEASE]",
@ -51,25 +44,7 @@ func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
Long: releaseTestDesc, Long: releaseTestDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.name = args[0] c, errc := client.Run(args[0])
o.client = ensureHelmClient(o.client, false)
return o.run(out)
},
}
f := cmd.Flags()
f.Int64Var(&o.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&o.cleanup, "cleanup", false, "delete test pods upon completion")
return cmd
}
func (o *releaseTestOptions) run(out io.Writer) (err error) {
c, errc := o.client.RunReleaseTest(
o.name,
helm.ReleaseTestTimeout(o.timeout),
helm.ReleaseTestCleanup(o.cleanup),
)
testErr := &testErr{} testErr := &testErr{}
for { for {
@ -90,6 +65,14 @@ func (o *releaseTestOptions) run(out io.Writer) (err error) {
fmt.Fprintf(out, res.Msg+"\n") fmt.Fprintf(out, res.Msg+"\n")
} }
} }
},
}
f := cmd.Flags()
f.Int64Var(&client.Timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&client.Cleanup, "cleanup", false, "delete test pods upon completion")
return cmd
} }
type testErr struct { type testErr struct {

@ -18,49 +18,79 @@ package main
import ( import (
"testing" "testing"
"time"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/release"
) )
func TestReleaseTesting(t *testing.T) { func TestReleaseTesting(t *testing.T) {
timestamp := time.Unix(1452902400, 0).UTC()
tests := []cmdTestCase{{ tests := []cmdTestCase{{
name: "basic test", name: "successful test",
cmd: "test example-release", cmd: "status test-success",
testRunStatus: map[string]release.TestRunStatus{"PASSED: green lights everywhere": release.TestRunSuccess}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{
golden: "output/test.txt", Name: "test-success",
TestSuiteResults: []*release.TestRun{
{
Name: "test-success",
Status: release.TestRunSuccess,
StartedAt: timestamp,
CompletedAt: timestamp,
},
},
})},
golden: "output/test-success.txt",
}, { }, {
name: "test failure", name: "test failure",
cmd: "test example-fail", cmd: "status test-failure",
testRunStatus: map[string]release.TestRunStatus{"FAILURE: red lights everywhere": release.TestRunFailure}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{
wantError: true, Name: "test-failure",
TestSuiteResults: []*release.TestRun{
{
Name: "test-failure",
Status: release.TestRunFailure,
StartedAt: timestamp,
CompletedAt: timestamp,
},
},
})},
golden: "output/test-failure.txt", golden: "output/test-failure.txt",
}, { }, {
name: "test unknown", name: "test unknown",
cmd: "test example-unknown", cmd: "status test-unknown",
testRunStatus: map[string]release.TestRunStatus{"UNKNOWN: yellow lights everywhere": release.TestRunUnknown}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{
Name: "test-unknown",
TestSuiteResults: []*release.TestRun{
{
Name: "test-unknown",
Status: release.TestRunUnknown,
StartedAt: timestamp,
CompletedAt: timestamp,
},
},
})},
golden: "output/test-unknown.txt", golden: "output/test-unknown.txt",
}, {
name: "test error",
cmd: "test example-error",
testRunStatus: map[string]release.TestRunStatus{"ERROR: yellow lights everywhere": release.TestRunFailure},
wantError: true,
golden: "output/test-error.txt",
}, { }, {
name: "test running", name: "test running",
cmd: "test example-running", cmd: "status test-running",
testRunStatus: map[string]release.TestRunStatus{"RUNNING: things are happpeningggg": release.TestRunRunning}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{
Name: "test-running",
TestSuiteResults: []*release.TestRun{
{
Name: "test-running",
Status: release.TestRunRunning,
StartedAt: timestamp,
CompletedAt: timestamp,
},
},
})},
golden: "output/test-running.txt", golden: "output/test-running.txt",
}, { }, {
name: "multiple tests example", name: "test with no tests",
cmd: "test example-suite", cmd: "test no-tests",
testRunStatus: map[string]release.TestRunStatus{ rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "no-tests"})},
"RUNNING: things are happpeningggg": release.TestRunRunning, golden: "output/test-no-tests.txt",
"PASSED: party time": release.TestRunSuccess,
"RUNNING: things are happening again": release.TestRunRunning,
"FAILURE: good thing u checked :)": release.TestRunFailure,
"RUNNING: things are happpeningggg yet again": release.TestRunRunning,
"PASSED: feel free to party again": release.TestRunSuccess},
wantError: true,
}} }}
runTestCmd(t, tests) runTestCmd(t, tests)
} }

@ -25,7 +25,7 @@ import (
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
) )

@ -25,7 +25,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
) )

@ -25,7 +25,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
) )

@ -26,7 +26,7 @@ import (
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
) )

@ -24,7 +24,7 @@ import (
"testing" "testing"
"k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/repo/repotest" "k8s.io/helm/pkg/repo/repotest"
) )

@ -19,13 +19,11 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"strconv"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/action"
) )
const rollbackDesc = ` const rollbackDesc = `
@ -36,20 +34,8 @@ second is a revision (version) number. To see revision numbers, run
'helm history RELEASE'. 'helm history RELEASE'.
` `
type rollbackOptions struct { func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
name string client := action.NewRollback(cfg)
revision int
dryRun bool
recreate bool
force bool
disableHooks bool
client helm.Interface
timeout int64
wait bool
}
func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
o := &rollbackOptions{client: c}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "rollback [RELEASE] [REVISION]", Use: "rollback [RELEASE] [REVISION]",
@ -57,45 +43,25 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
Long: rollbackDesc, Long: rollbackDesc,
Args: require.ExactArgs(2), Args: require.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.name = args[0] _, err := client.Run(args[0])
v64, err := strconv.ParseInt(args[1], 10, 32)
if err != nil { if err != nil {
return errors.Wrapf(err, "invalid revision number '%q'", args[1]) return err
} }
o.revision = int(v64) fmt.Fprintf(out, "Rollback was a success! Happy Helming!\n")
o.client = ensureHelmClient(o.client, false)
return o.run(out) return nil
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&o.dryRun, "dry-run", false, "simulate a rollback") f.IntVarP(&client.Version, "version", "v", 0, "revision number to rollback to (default: rollback to previous release)")
f.BoolVar(&o.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback")
f.BoolVar(&o.force, "force", false, "force resource update through delete/recreate if needed") f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&o.disableHooks, "no-hooks", false, "prevent hooks from running during rollback") f.BoolVar(&client.Force, "force", false, "force resource update through delete/recreate if needed")
f.Int64Var(&o.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during rollback")
f.BoolVar(&o.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.Int64Var(&client.Timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
return cmd return cmd
} }
func (o *rollbackOptions) run(out io.Writer) error {
_, err := o.client.RollbackRelease(
o.name,
helm.RollbackDryRun(o.dryRun),
helm.RollbackRecreate(o.recreate),
helm.RollbackForce(o.force),
helm.RollbackDisableHooks(o.disableHooks),
helm.RollbackVersion(o.revision),
helm.RollbackTimeout(o.timeout),
helm.RollbackWait(o.wait))
if err != nil {
return err
}
fmt.Fprintf(out, "Rollback was a success! Happy Helming!\n")
return nil
}

@ -18,25 +18,47 @@ package main
import ( import (
"testing" "testing"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/release"
) )
func TestRollbackCmd(t *testing.T) { func TestRollbackCmd(t *testing.T) {
rels := []*release.Release{
{
Name: "funny-honey",
Info: &release.Info{Status: release.StatusSuperseded},
Chart: &chart.Chart{},
Version: 1,
},
{
Name: "funny-honey",
Info: &release.Info{Status: release.StatusDeployed},
Chart: &chart.Chart{},
Version: 2,
},
}
tests := []cmdTestCase{{ tests := []cmdTestCase{{
name: "rollback a release", name: "rollback a release",
cmd: "rollback funny-honey 1", cmd: "rollback funny-honey 1",
golden: "output/rollback.txt", golden: "output/rollback.txt",
rels: rels,
}, { }, {
name: "rollback a release with timeout", name: "rollback a release with timeout",
cmd: "rollback funny-honey 1 --timeout 120", cmd: "rollback funny-honey 1 --timeout 120",
golden: "output/rollback-timeout.txt", golden: "output/rollback-timeout.txt",
rels: rels,
}, { }, {
name: "rollback a release with wait", name: "rollback a release with wait",
cmd: "rollback funny-honey 1 --wait", cmd: "rollback funny-honey 1 --wait",
golden: "output/rollback-wait.txt", golden: "output/rollback-wait.txt",
rels: rels,
}, { }, {
name: "rollback a release without revision", name: "rollback a release without revision",
cmd: "rollback funny-honey", cmd: "rollback funny-honey",
golden: "output/rollback-no-args.txt", golden: "output/rollback-no-args.txt",
rels: rels,
wantError: true, wantError: true,
}} }}
runTestCmd(t, tests) runTestCmd(t, tests)

@ -24,7 +24,6 @@ import (
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/action" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/registry" "k8s.io/helm/pkg/registry"
) )
@ -50,8 +49,7 @@ Environment:
$KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config") $KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config")
` `
// TODO: 'c helm.Interface' is deprecated in favor of actionConfig func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string) *cobra.Command {
func newRootCmd(c helm.Interface, actionConfig *action.Configuration, out io.Writer, args []string) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "helm", Use: "helm",
Short: "The Helm package manager for Kubernetes.", Short: "The Helm package manager for Kubernetes.",
@ -92,15 +90,15 @@ func newRootCmd(c helm.Interface, actionConfig *action.Configuration, out io.Wri
newChartCmd(actionConfig, out), newChartCmd(actionConfig, out),
// release commands // release commands
newGetCmd(c, out), newGetCmd(actionConfig, out),
newHistoryCmd(c, out), newHistoryCmd(actionConfig, out),
newInstallCmd(actionConfig, out), newInstallCmd(actionConfig, out),
newListCmd(actionConfig, out), newListCmd(actionConfig, out),
newReleaseTestCmd(c, out), newReleaseTestCmd(actionConfig, out),
newRollbackCmd(c, out), newRollbackCmd(actionConfig, out),
newStatusCmd(c, out), newStatusCmd(actionConfig, out),
newUninstallCmd(c, out), newUninstallCmd(actionConfig, out),
newUpgradeCmd(c, out), newUpgradeCmd(actionConfig, out),
newCompletionCmd(out), newCompletionCmd(out),
newHomeCmd(out), newHomeCmd(out),

@ -77,7 +77,7 @@ func TestRootCmd(t *testing.T) {
os.Setenv(k, v) os.Setenv(k, v)
} }
cmd, _, err := executeCommandC(nil, tt.args) cmd, _, err := executeActionCommand(tt.args)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %s", err) t.Fatalf("unexpected error: %s", err)
} }

@ -27,7 +27,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/search" "k8s.io/helm/cmd/helm/search"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/repo"
) )

@ -19,14 +19,11 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"strings"
"github.com/ghodss/yaml"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/chart/loader"
) )
const showDesc = ` const showDesc = `
@ -51,24 +48,8 @@ This command inspects a chart (directory, file, or URL) and displays the content
of the README file of the README file
` `
type showOptions struct {
chartpath string
output string
chartPathOptions
}
const (
chartOnly = "chart"
valuesOnly = "values"
readmeOnly = "readme"
all = "all"
)
var readmeFileNames = []string{"readme.md", "readme.txt", "readme"}
func newShowCmd(out io.Writer) *cobra.Command { func newShowCmd(out io.Writer) *cobra.Command {
o := &showOptions{output: all} client := action.NewShow(action.ShowAll)
showCommand := &cobra.Command{ showCommand := &cobra.Command{
Use: "show [CHART]", Use: "show [CHART]",
@ -77,12 +58,16 @@ func newShowCmd(out io.Writer) *cobra.Command {
Long: showDesc, Long: showDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
cp, err := o.locateChart(args[0]) cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil { if err != nil {
return err return err
} }
o.chartpath = cp output, err := client.Run(cp)
return o.run(out) if err != nil {
return err
}
fmt.Fprint(out, output)
return nil
}, },
} }
@ -92,13 +77,17 @@ func newShowCmd(out io.Writer) *cobra.Command {
Long: showValuesDesc, Long: showValuesDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.output = valuesOnly client.OutputFormat = action.ShowValues
cp, err := o.locateChart(args[0]) cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil {
return err
}
output, err := client.Run(cp)
if err != nil { if err != nil {
return err return err
} }
o.chartpath = cp fmt.Fprint(out, output)
return o.run(out) return nil
}, },
} }
@ -108,13 +97,17 @@ func newShowCmd(out io.Writer) *cobra.Command {
Long: showChartDesc, Long: showChartDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.output = chartOnly client.OutputFormat = action.ShowChart
cp, err := o.locateChart(args[0]) cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil {
return err
}
output, err := client.Run(cp)
if err != nil { if err != nil {
return err return err
} }
o.chartpath = cp fmt.Fprint(out, output)
return o.run(out) return nil
}, },
} }
@ -124,19 +117,23 @@ func newShowCmd(out io.Writer) *cobra.Command {
Long: readmeChartDesc, Long: readmeChartDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.output = readmeOnly client.OutputFormat = action.ShowReadme
cp, err := o.locateChart(args[0]) cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil {
return err
}
output, err := client.Run(cp)
if err != nil { if err != nil {
return err return err
} }
o.chartpath = cp fmt.Fprint(out, output)
return o.run(out) return nil
}, },
} }
cmds := []*cobra.Command{showCommand, readmeSubCmd, valuesSubCmd, chartSubCmd} cmds := []*cobra.Command{showCommand, readmeSubCmd, valuesSubCmd, chartSubCmd}
for _, subCmd := range cmds { for _, subCmd := range cmds {
o.chartPathOptions.addFlags(subCmd.Flags()) addChartPathOptionsFlags(subCmd.Flags(), &client.ChartPathOptions)
} }
for _, subCmd := range cmds[1:] { for _, subCmd := range cmds[1:] {
@ -145,52 +142,3 @@ func newShowCmd(out io.Writer) *cobra.Command {
return showCommand return showCommand
} }
func (i *showOptions) run(out io.Writer) error {
chrt, err := loader.Load(i.chartpath)
if err != nil {
return err
}
cf, err := yaml.Marshal(chrt.Metadata)
if err != nil {
return err
}
if i.output == chartOnly || i.output == all {
fmt.Fprintln(out, string(cf))
}
if (i.output == valuesOnly || i.output == all) && chrt.Values != nil {
if i.output == all {
fmt.Fprintln(out, "---")
}
b, err := yaml.Marshal(chrt.Values)
if err != nil {
return err
}
fmt.Fprintln(out, string(b))
}
if i.output == readmeOnly || i.output == all {
if i.output == all {
fmt.Fprintln(out, "---")
}
readme := findReadme(chrt.Files)
if readme == nil {
return nil
}
fmt.Fprintln(out, string(readme.Data))
}
return nil
}
func findReadme(files []*chart.File) (file *chart.File) {
for _, file := range files {
for _, n := range readmeFileNames {
if strings.EqualFold(file.Name, n) {
return file
}
}
}
return nil
}

@ -18,20 +18,14 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"regexp"
"text/tabwriter"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/gosuri/uitable"
"github.com/gosuri/uitable/util/strutil"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/helm"
) )
var statusHelp = ` var statusHelp = `
@ -45,15 +39,8 @@ The status consists of:
- additional notes provided by the chart - additional notes provided by the chart
` `
type statusOptions struct { func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
release string client := action.NewStatus(cfg)
client helm.Interface
version int
outfmt string
}
func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command {
o := &statusOptions{client: client}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "status RELEASE_NAME", Use: "status RELEASE_NAME",
@ -61,88 +48,44 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command {
Long: statusHelp, Long: statusHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.release = args[0] rel, err := client.Run(args[0])
o.client = ensureHelmClient(o.client, false) if err != nil {
return o.run(out) return err
},
}
cmd.PersistentFlags().IntVar(&o.version, "revision", 0, "if set, display the status of the named release with revision")
cmd.PersistentFlags().StringVarP(&o.outfmt, "output", "o", "", "output the status in the specified format (json or yaml)")
return cmd
} }
func (o *statusOptions) run(out io.Writer) error { outfmt, err := action.ParseOutputFormat(client.OutputFormat)
res, err := o.client.ReleaseContent(o.release, o.version) // We treat an invalid format type as the default
if err != nil { if err != nil && err != action.ErrInvalidFormatType {
return err return err
} }
switch o.outfmt { switch outfmt {
case "": case "":
PrintStatus(out, res) action.PrintRelease(out, rel)
return nil return nil
case "json": case action.JSON:
data, err := json.Marshal(res) data, err := json.Marshal(rel)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to Marshal JSON output") return errors.Wrap(err, "failed to Marshal JSON output")
} }
out.Write(data) out.Write(data)
return nil return nil
case "yaml": case action.YAML:
data, err := yaml.Marshal(res) data, err := yaml.Marshal(rel)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to Marshal YAML output") return errors.Wrap(err, "failed to Marshal YAML output")
} }
out.Write(data) out.Write(data)
return nil return nil
default:
return errors.Errorf("unknown output format %q", outfmt)
} }
},
return errors.Errorf("unknown output format %q", o.outfmt)
}
// PrintStatus prints out the status of a release. Shared because also used by
// install / upgrade
func PrintStatus(out io.Writer, res *release.Release) {
if !res.Info.LastDeployed.IsZero() {
fmt.Fprintf(out, "LAST DEPLOYED: %s\n", res.Info.LastDeployed)
}
fmt.Fprintf(out, "NAMESPACE: %s\n", res.Namespace)
fmt.Fprintf(out, "STATUS: %s\n", res.Info.Status.String())
fmt.Fprintf(out, "\n")
if len(res.Info.Resources) > 0 {
re := regexp.MustCompile(" +")
w := tabwriter.NewWriter(out, 0, 0, 2, ' ', tabwriter.TabIndent)
fmt.Fprintf(w, "RESOURCES:\n%s\n", re.ReplaceAllString(res.Info.Resources, "\t"))
w.Flush()
}
if res.Info.LastTestSuiteRun != nil {
lastRun := res.Info.LastTestSuiteRun
fmt.Fprintf(out, "TEST SUITE:\n%s\n%s\n\n%s\n",
fmt.Sprintf("Last Started: %s", lastRun.StartedAt),
fmt.Sprintf("Last Completed: %s", lastRun.CompletedAt),
formatTestResults(lastRun.Results))
} }
if len(res.Info.Notes) > 0 { f := cmd.PersistentFlags()
fmt.Fprintf(out, "NOTES:\n%s\n", res.Info.Notes) f.IntVar(&client.Version, "revision", 0, "if set, display the status of the named release with revision")
} f.StringVarP(&client.OutputFormat, "output", "o", "", "output the status in the specified format (json or yaml)")
}
func formatTestResults(results []*release.TestRun) string { return cmd
tbl := uitable.New()
tbl.MaxColWidth = 50
tbl.AddRow("TEST", "STATUS", "INFO", "STARTED", "COMPLETED")
for i := 0; i < len(results); i++ {
r := results[i]
n := r.Name
s := strutil.PadRight(r.Status.String(), 10, ' ')
i := r.Info
ts := r.StartedAt
tc := r.CompletedAt
tbl.AddRow(n, s, i, ts, tc)
}
return tbl.String()
} }

@ -20,7 +20,9 @@ import (
"testing" "testing"
"time" "time"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/release"
) )
func TestStatusCmd(t *testing.T) { func TestStatusCmd(t *testing.T) {
@ -29,6 +31,7 @@ func TestStatusCmd(t *testing.T) {
return []*release.Release{{ return []*release.Release{{
Name: "flummoxed-chickadee", Name: "flummoxed-chickadee",
Info: info, Info: info,
Chart: &chart.Chart{},
}} }}
} }

@ -19,39 +19,23 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"os" "io/ioutil"
"path"
"path/filepath"
"regexp"
"strings" "strings"
"time"
"github.com/Masterminds/semver"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/engine" "k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/storage/driver"
util "k8s.io/helm/pkg/releaseutil"
"k8s.io/helm/pkg/tiller"
)
const defaultDirectoryPermission = 0755
var (
whitespaceRegex = regexp.MustCompile(`^\s*$`)
// defaultKubeVersion is the default value of --kube-version flag
defaultKubeVersion = fmt.Sprintf("%s.%s", chartutil.DefaultKubeVersion.Major, chartutil.DefaultKubeVersion.Minor)
) )
const templateDesc = ` const templateDesc = `
Render chart templates locally and display the output. Render chart templates locally and display the output.
This does not require Tiller. However, any values that would normally be This does not require Helm. However, any values that would normally be
looked up or retrieved in-cluster will be faked locally. Additionally, none looked up or retrieved in-cluster will be faked locally. Additionally, none
of the server-side testing of chart validity (e.g. whether an API is supported) of the server-side testing of chart validity (e.g. whether an API is supported)
is done. is done.
@ -61,232 +45,38 @@ To render just one template in a chart, use '-x':
$ helm template mychart -x templates/deployment.yaml $ helm template mychart -x templates/deployment.yaml
` `
type templateOptions struct { func newTemplateCmd(out io.Writer) *cobra.Command {
nameTemplate string // --name-template customConfig := &action.Configuration{
showNotes bool // --notes // Add mock objects in here so it doesn't use Kube API server
releaseName string // --name Releases: storage.Init(driver.NewMemory()),
renderFiles []string // --execute KubeClient: &kube.PrintingKubeClient{Out: ioutil.Discard},
kubeVersion string // --kube-version Discovery: fake.NewSimpleClientset().Discovery(),
outputDir string // --output-dir Log: func(format string, v ...interface{}) {
fmt.Fprintf(out, format, v...)
valuesOptions },
chartPath string
} }
func newTemplateCmd(out io.Writer) *cobra.Command { client := action.NewInstall(customConfig)
o := &templateOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "template CHART", Use: "template CHART",
Short: fmt.Sprintf("locally render templates"), Short: fmt.Sprintf("locally render templates"),
Long: templateDesc, Long: templateDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
// verify chart path exists client.DryRun = true
if _, err := os.Stat(args[0]); err == nil { client.ReleaseName = "RELEASE-NAME"
if o.chartPath, err = filepath.Abs(args[0]); err != nil { client.Replace = true // Skip the name check
return err rel, err := runInstall(args, client, out)
}
} else {
return err
}
return o.run(out)
},
}
f := cmd.Flags()
f.BoolVar(&o.showNotes, "notes", false, "show the computed NOTES.txt file as well")
f.StringVarP(&o.releaseName, "name", "", "RELEASE-NAME", "release name")
f.StringArrayVarP(&o.renderFiles, "execute", "x", []string{}, "only execute the given templates")
f.StringVar(&o.nameTemplate, "name-template", "", "specify template used to name the release")
f.StringVar(&o.kubeVersion, "kube-version", defaultKubeVersion, "kubernetes version used as Capabilities.KubeVersion.Major/Minor")
f.StringVar(&o.outputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
o.valuesOptions.addFlags(f)
return cmd
}
func (o *templateOptions) run(out io.Writer) error {
// verify specified templates exist relative to chart
rf := []string{}
var af string
var err error
if len(o.renderFiles) > 0 {
for _, f := range o.renderFiles {
if !filepath.IsAbs(f) {
af, err = filepath.Abs(filepath.Join(o.chartPath, f))
if err != nil { if err != nil {
return errors.Wrap(err, "could not resolve template path")
}
} else {
af = f
}
rf = append(rf, af)
if _, err := os.Stat(af); err != nil {
return errors.Wrap(err, "could not resolve template path")
}
}
}
// verify that output-dir exists if provided
if o.outputDir != "" {
if _, err := os.Stat(o.outputDir); os.IsNotExist(err) {
return errors.Errorf("output-dir '%s' does not exist", o.outputDir)
}
}
// get combined values and create config
config, err := o.mergedValues()
if err != nil {
return err
}
// If template is specified, try to run the template.
if o.nameTemplate != "" {
o.releaseName, err = templateName(o.nameTemplate)
if err != nil {
return err
}
}
// Check chart dependencies to make sure all are present in /charts
c, err := loader.Load(o.chartPath)
if err != nil {
return err
}
validInstallableChart, err := chartutil.IsChartInstallable(c)
if !validInstallableChart {
return err return err
} }
fmt.Fprintln(out, strings.TrimSpace(rel.Manifest))
if req := c.Metadata.Dependencies; req != nil {
if err := checkDependencies(c, req); err != nil {
return err
}
}
options := chartutil.ReleaseOptions{
Name: o.releaseName,
}
if err := chartutil.ProcessDependencies(c, config); err != nil {
return err
}
// kubernetes version
kv, err := semver.NewVersion(o.kubeVersion)
if err != nil {
return errors.Wrap(err, "could not parse a kubernetes version")
}
caps := chartutil.DefaultCapabilities
caps.KubeVersion.Major = fmt.Sprint(kv.Major())
caps.KubeVersion.Minor = fmt.Sprint(kv.Minor())
caps.KubeVersion.GitVersion = fmt.Sprintf("v%d.%d.0", kv.Major(), kv.Minor())
vals, err := chartutil.ToRenderValues(c, config, options, caps)
if err != nil {
return err
}
rendered, err := engine.Render(c, vals)
if err != nil {
return err
}
listManifests := []tiller.Manifest{}
// extract kind and name
re := regexp.MustCompile("kind:(.*)\n")
for k, v := range rendered {
match := re.FindStringSubmatch(v)
h := "Unknown"
if len(match) == 2 {
h = strings.TrimSpace(match[1])
}
m := tiller.Manifest{Name: k, Content: v, Head: &util.SimpleHead{Kind: h}}
listManifests = append(listManifests, m)
}
in := func(needle string, haystack []string) bool {
// make needle path absolute
// NOTE: chart manifest names always use backslashes as path separators, even on Windows
d := strings.Split(needle, "/")
dd := d[1:]
an := filepath.Join(o.chartPath, strings.Join(dd, string(os.PathSeparator)))
for _, h := range haystack {
if h == an {
return true
}
}
return false
}
if settings.Debug {
rel := &release.Release{
Name: o.releaseName,
Chart: c,
Config: config,
Version: 1,
Info: &release.Info{LastDeployed: time.Now()},
}
printRelease(out, rel)
}
for _, m := range tiller.SortByKind(listManifests) {
b := filepath.Base(m.Name)
switch {
case len(o.renderFiles) > 0 && !in(m.Name, rf):
continue
case !o.showNotes && b == "NOTES.txt":
continue
case strings.HasPrefix(b, "_"):
continue
case whitespaceRegex.MatchString(m.Content):
// blank template after execution
continue
case o.outputDir != "":
if err := writeToFile(out, o.outputDir, m.Name, m.Content); err != nil {
return err
}
default:
fmt.Fprintf(out, "---\n# Source: %s\n", m.Name)
fmt.Fprintln(out, m.Content)
}
}
return nil
}
// write the <data> to <output-dir>/<name>
func writeToFile(out io.Writer, outputDir, name, data string) error {
outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator))
if err := ensureDirectoryForFile(outfileName); err != nil {
return err
}
f, err := os.Create(outfileName)
if err != nil {
return err
}
defer f.Close()
if _, err = f.WriteString(fmt.Sprintf("##---\n# Source: %s\n%s", name, data)); err != nil {
return err
}
fmt.Fprintf(out, "wrote %s\n", outfileName)
return nil return nil
},
} }
// check if the directory exists to create file. creates if don't exists addInstallFlags(cmd.Flags(), client)
func ensureDirectoryForFile(file string) error {
baseDir := path.Dir(file)
if _, err := os.Stat(baseDir); err != nil && !os.IsNotExist(err) {
return err
}
return os.MkdirAll(baseDir, defaultDirectoryPermission) return cmd
} }

@ -25,10 +25,6 @@ import (
var chartPath = "./../../pkg/chartutil/testdata/subpop/charts/subchart1" var chartPath = "./../../pkg/chartutil/testdata/subpop/charts/subchart1"
func TestTemplateCmd(t *testing.T) { func TestTemplateCmd(t *testing.T) {
absChartPath, err := filepath.Abs(chartPath)
if err != nil {
t.Fatal(err)
}
tests := []cmdTestCase{ tests := []cmdTestCase{
{ {
name: "check name", name: "check name",
@ -37,24 +33,9 @@ func TestTemplateCmd(t *testing.T) {
}, },
{ {
name: "check set name", name: "check set name",
cmd: fmt.Sprintf("template '%s' -x '%s' --set service.name=apache", chartPath, filepath.Join("templates", "service.yaml")), cmd: fmt.Sprintf("template '%s' --set service.name=apache", chartPath),
golden: "output/template-set.txt", golden: "output/template-set.txt",
}, },
{
name: "check execute absolute",
cmd: fmt.Sprintf("template '%s' -x '%s' --set service.name=apache", chartPath, filepath.Join(absChartPath, "templates", "service.yaml")),
golden: "output/template-absolute.txt",
},
{
name: "check release name",
cmd: fmt.Sprintf("template '%s' --name test", chartPath),
golden: "output/template-name.txt",
},
{
name: "check notes",
cmd: fmt.Sprintf("template '%s' --notes", chartPath),
golden: "output/template-notes.txt",
},
{ {
name: "check values files", name: "check values files",
cmd: fmt.Sprintf("template '%s' --values '%s'", chartPath, filepath.Join(chartPath, "/charts/subchartA/values.yaml")), cmd: fmt.Sprintf("template '%s' --values '%s'", chartPath, filepath.Join(chartPath, "/charts/subchartA/values.yaml")),
@ -65,11 +46,6 @@ func TestTemplateCmd(t *testing.T) {
cmd: fmt.Sprintf(`template '%s' --name-template='foobar-{{ b64enc "abc" }}-baz'`, chartPath), cmd: fmt.Sprintf(`template '%s' --name-template='foobar-{{ b64enc "abc" }}-baz'`, chartPath),
golden: "output/template-name-template.txt", golden: "output/template-name-template.txt",
}, },
{
name: "check kube version",
cmd: fmt.Sprintf("template '%s' --kube-version 1.6", chartPath),
golden: "output/template-kube-version.txt",
},
{ {
name: "check no args", name: "check no args",
cmd: "template", cmd: "template",

@ -1 +0,0 @@
Error: FindFirstFile \no\such\chart: The system cannot find the path specified.

@ -1 +0,0 @@
WARNING: no dependencies at testdata\testcharts\alpine\charts

@ -1,4 +1,3 @@
FINAL NAME: FOOBAR
NAME: FOOBAR NAME: FOOBAR
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default

@ -1,2 +0,0 @@
atlas-guide
thomas-guide

@ -1,2 +0,0 @@
atlas-guide
thomas-guide

@ -1,2 +0,0 @@
atlas-guide
thomas-guide

@ -1,3 +0,0 @@
NAME REVISION UPDATED STATUS CHART NAMESPACE
thomas-guide 1 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 default
thomas-guide 2 1977-09-02 22:04:05 +0000 UTC failed foo-0.1.0-beta.1 default

@ -1,4 +0,0 @@
atlas-guide
crazy-maps
thomas-guide
wild-idea

@ -1,2 +0,0 @@
NAME REVISION UPDATED STATUS CHART NAMESPACE
thomas-guide 1 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 default

@ -1,2 +0,0 @@
NAME REVISION UPDATED STATUS CHART NAMESPACE
atlas 1 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 default

@ -1,3 +1,4 @@
NAME: flummoxed-chickadee
LAST DEPLOYED: 2016-01-16 00:00:00 +0000 UTC LAST DEPLOYED: 2016-01-16 00:00:00 +0000 UTC
NAMESPACE: NAMESPACE:
STATUS: deployed STATUS: deployed

@ -1,3 +1,4 @@
NAME: flummoxed-chickadee
LAST DEPLOYED: 2016-01-16 00:00:00 +0000 UTC LAST DEPLOYED: 2016-01-16 00:00:00 +0000 UTC
NAMESPACE: NAMESPACE:
STATUS: deployed STATUS: deployed

@ -1,3 +1,4 @@
NAME: flummoxed-chickadee
LAST DEPLOYED: 2016-01-16 00:00:00 +0000 UTC LAST DEPLOYED: 2016-01-16 00:00:00 +0000 UTC
NAMESPACE: NAMESPACE:
STATUS: deployed STATUS: deployed

@ -1,3 +1,4 @@
NAME: flummoxed-chickadee
LAST DEPLOYED: 2016-01-16 00:00:00 +0000 UTC LAST DEPLOYED: 2016-01-16 00:00:00 +0000 UTC
NAMESPACE: NAMESPACE:
STATUS: deployed STATUS: deployed

@ -1,22 +0,0 @@
---
# Source: subchart1/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchart1
labels:
chart: "subchart1-0.1.0"
release-name: "RELEASE-NAME"
kube-version/major: "1"
kube-version/minor: "9"
kube-version/gitversion: "v1.9.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app: subchart1

@ -1,58 +0,0 @@
---
# Source: subchart1/charts/subcharta/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subcharta
labels:
chart: "subcharta-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app: subcharta
---
# Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchartb
labels:
chart: "subchartb-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app: subchartb
---
# Source: subchart1/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchart1
labels:
chart: "subchart1-0.1.0"
release-name: "RELEASE-NAME"
kube-version/major: "1"
kube-version/minor: "6"
kube-version/gitversion: "v1.6.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app: subchart1

@ -15,7 +15,6 @@ spec:
name: apache name: apache
selector: selector:
app: subcharta app: subcharta
--- ---
# Source: subchart1/charts/subchartb/templates/service.yaml # Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -33,7 +32,6 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchartb app: subchartb
--- ---
# Source: subchart1/templates/service.yaml # Source: subchart1/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -55,4 +53,3 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchart1 app: subchart1

@ -1,58 +0,0 @@
---
# Source: subchart1/charts/subcharta/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subcharta
labels:
chart: "subcharta-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app: subcharta
---
# Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchartb
labels:
chart: "subchartb-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app: subchartb
---
# Source: subchart1/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchart1
labels:
chart: "subchart1-0.1.0"
release-name: "test"
kube-version/major: "1"
kube-version/minor: "9"
kube-version/gitversion: "v1.9.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app: subchart1

@ -1,61 +0,0 @@
---
# Source: subchart1/charts/subcharta/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subcharta
labels:
chart: "subcharta-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app: subcharta
---
# Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchartb
labels:
chart: "subchartb-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app: subchartb
---
# Source: subchart1/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchart1
labels:
chart: "subchart1-0.1.0"
release-name: "RELEASE-NAME"
kube-version/major: "1"
kube-version/minor: "9"
kube-version/gitversion: "v1.9.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app: subchart1
---
# Source: subchart1/templates/NOTES.txt
Sample notes for subchart1

@ -1,4 +1,38 @@
--- ---
# Source: subchart1/charts/subcharta/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subcharta
labels:
chart: "subcharta-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app: subcharta
---
# Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchartb
labels:
chart: "subchartb-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app: subchartb
---
# Source: subchart1/templates/service.yaml # Source: subchart1/templates/service.yaml
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
@ -19,4 +53,3 @@ spec:
name: apache name: apache
selector: selector:
app: subchart1 app: subchart1

@ -15,7 +15,6 @@ spec:
name: apache name: apache
selector: selector:
app: subcharta app: subcharta
--- ---
# Source: subchart1/charts/subchartb/templates/service.yaml # Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -33,7 +32,6 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchartb app: subchartb
--- ---
# Source: subchart1/templates/service.yaml # Source: subchart1/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -55,4 +53,3 @@ spec:
name: apache name: apache
selector: selector:
app: subchart1 app: subchart1

@ -15,7 +15,6 @@ spec:
name: apache name: apache
selector: selector:
app: subcharta app: subcharta
--- ---
# Source: subchart1/charts/subchartb/templates/service.yaml # Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -33,7 +32,6 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchartb app: subchartb
--- ---
# Source: subchart1/templates/service.yaml # Source: subchart1/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -55,4 +53,3 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchart1 app: subchart1

@ -1,2 +0,0 @@
ERROR: yellow lights everywhere
Error: 1 test(s) failed

@ -1,2 +1,11 @@
FAILURE: red lights everywhere NAME: test-failure
Error: 1 test(s) failed LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed
TEST SUITE:
Last Started: 1977-09-02 22:04:05 +0000 UTC
Last Completed: 1977-09-02 22:04:05 +0000 UTC
TEST STATUS INFO STARTED COMPLETED
test-failure failure 2016-01-16 00:00:00 +0000 UTC 2016-01-16 00:00:00 +0000 UTC

@ -1 +1,11 @@
RUNNING: things are happpeningggg NAME: test-running
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed
TEST SUITE:
Last Started: 1977-09-02 22:04:05 +0000 UTC
Last Completed: 1977-09-02 22:04:05 +0000 UTC
TEST STATUS INFO STARTED COMPLETED
test-running running 2016-01-16 00:00:00 +0000 UTC 2016-01-16 00:00:00 +0000 UTC

@ -0,0 +1,11 @@
NAME: test-success
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed
TEST SUITE:
Last Started: 1977-09-02 22:04:05 +0000 UTC
Last Completed: 1977-09-02 22:04:05 +0000 UTC
TEST STATUS INFO STARTED COMPLETED
test-success success 2016-01-16 00:00:00 +0000 UTC 2016-01-16 00:00:00 +0000 UTC

@ -1 +1,11 @@
UNKNOWN: yellow lights everywhere NAME: test-unknown
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed
TEST SUITE:
Last Started: 1977-09-02 22:04:05 +0000 UTC
Last Completed: 1977-09-02 22:04:05 +0000 UTC
TEST STATUS INFO STARTED COMPLETED
test-unknown unknown 2016-01-16 00:00:00 +0000 UTC 2016-01-16 00:00:00 +0000 UTC

@ -1 +0,0 @@
PASSED: green lights everywhere

@ -1,5 +1,11 @@
Release "crazy-bunny" has been upgraded. Happy Helming! Release "crazy-bunny" has been upgraded. Happy Helming!
NAME: crazy-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=crazy-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -1,5 +1,11 @@
Release "zany-bunny" has been upgraded. Happy Helming! Release "zany-bunny" has been upgraded. Happy Helming!
NAME: zany-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=zany-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -1,5 +1,11 @@
Release "funny-bunny" has been upgraded. Happy Helming! Release "funny-bunny" has been upgraded. Happy Helming!
NAME: funny-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=funny-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -1,5 +1,11 @@
Release "funny-bunny" has been upgraded. Happy Helming! Release "funny-bunny" has been upgraded. Happy Helming!
NAME: funny-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=funny-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -1,5 +1,11 @@
Release "funny-bunny" has been upgraded. Happy Helming! Release "funny-bunny" has been upgraded. Happy Helming!
NAME: funny-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=funny-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -1,5 +1,11 @@
Release "crazy-bunny" has been upgraded. Happy Helming! Release "crazy-bunny" has been upgraded. Happy Helming!
NAME: crazy-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=crazy-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -1,5 +1,11 @@
Release "funny-bunny" has been upgraded. Happy Helming! Release "funny-bunny" has been upgraded. Happy Helming!
NAME: funny-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods -l "app=testUpgradeChart,release=funny-bunny" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

@ -23,7 +23,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/action"
) )
const uninstallDesc = ` const uninstallDesc = `
@ -34,20 +34,8 @@ Use the '--dry-run' flag to see which releases will be uninstalled without actua
uninstalling them. uninstalling them.
` `
type uninstallOptions struct { func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
disableHooks bool // --no-hooks client := action.NewUninstall(cfg)
dryRun bool // --dry-run
purge bool // --purge
timeout int64 // --timeout
// args
name string
client helm.Interface
}
func newUninstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
o := &uninstallOptions{client: c}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "uninstall RELEASE_NAME [...]", Use: "uninstall RELEASE_NAME [...]",
@ -57,40 +45,27 @@ func newUninstallCmd(c helm.Interface, out io.Writer) *cobra.Command {
Long: uninstallDesc, Long: uninstallDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.client = ensureHelmClient(o.client, false)
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
o.name = args[i]
if err := o.run(out); err != nil { res, err := client.Run(args[0])
if err != nil {
return err return err
} }
if res != nil && res.Info != "" {
fmt.Fprintln(out, res.Info)
}
fmt.Fprintf(out, "release \"%s\" uninstalled\n", o.name) fmt.Fprintf(out, "release \"%s\" uninstalled\n", args[i])
} }
return nil return nil
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&o.dryRun, "dry-run", false, "simulate a uninstall") f.BoolVar(&client.DryRun, "dry-run", false, "simulate a uninstall")
f.BoolVar(&o.disableHooks, "no-hooks", false, "prevent hooks from running during uninstallation") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation")
f.BoolVar(&o.purge, "purge", false, "remove the release from the store and make its name free for later use") f.BoolVar(&client.Purge, "purge", false, "remove the release from the store and make its name free for later use")
f.Int64Var(&o.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") f.Int64Var(&client.Timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
return cmd return cmd
} }
func (o *uninstallOptions) run(out io.Writer) error {
opts := []helm.UninstallOption{
helm.UninstallDryRun(o.dryRun),
helm.UninstallDisableHooks(o.disableHooks),
helm.UninstallPurge(o.purge),
helm.UninstallTimeout(o.timeout),
}
res, err := o.client.UninstallRelease(o.name, opts...)
if res != nil && res.Info != "" {
fmt.Fprintln(out, res.Info)
}
return err
}

@ -19,38 +19,34 @@ package main
import ( import (
"testing" "testing"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/release"
"k8s.io/helm/pkg/helm"
) )
func TestUninstall(t *testing.T) { func TestUninstall(t *testing.T) {
rels := []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}
tests := []cmdTestCase{ tests := []cmdTestCase{
{ {
name: "basic uninstall", name: "basic uninstall",
cmd: "uninstall aeneas", cmd: "uninstall aeneas",
golden: "output/uninstall.txt", golden: "output/uninstall.txt",
rels: rels, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})},
}, },
{ {
name: "uninstall with timeout", name: "uninstall with timeout",
cmd: "uninstall aeneas --timeout 120", cmd: "uninstall aeneas --timeout 120",
golden: "output/uninstall-timeout.txt", golden: "output/uninstall-timeout.txt",
rels: rels, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})},
}, },
{ {
name: "uninstall without hooks", name: "uninstall without hooks",
cmd: "uninstall aeneas --no-hooks", cmd: "uninstall aeneas --no-hooks",
golden: "output/uninstall-no-hooks.txt", golden: "output/uninstall-no-hooks.txt",
rels: rels, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})},
}, },
{ {
name: "purge", name: "purge",
cmd: "uninstall aeneas --purge", cmd: "uninstall aeneas --purge",
golden: "output/uninstall-purge.txt", golden: "output/uninstall-purge.txt",
rels: rels, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})},
}, },
{ {
name: "uninstall without release", name: "uninstall without release",

@ -24,8 +24,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/storage/driver" "k8s.io/helm/pkg/storage/driver"
) )
@ -54,30 +54,8 @@ set for a key called 'foo', the 'newbar' value would take precedence:
$ helm upgrade --set foo=bar --set foo=newbar redis ./redis $ helm upgrade --set foo=bar --set foo=newbar redis ./redis
` `
type upgradeOptions struct { func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
devel bool // --devel client := action.NewUpgrade(cfg)
disableHooks bool // --disable-hooks
dryRun bool // --dry-run
force bool // --force
install bool // --install
recreate bool // --recreate-pods
resetValues bool // --reset-values
reuseValues bool // --reuse-values
timeout int64 // --timeout
wait bool // --wait
maxHistory int // --max-history
valuesOptions
chartPathOptions
release string
chart string
client helm.Interface
}
func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
o := &upgradeOptions{client: client}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "upgrade [RELEASE] [CHART]", Use: "upgrade [RELEASE] [CHART]",
@ -85,68 +63,43 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
Long: upgradeDesc, Long: upgradeDesc,
Args: require.ExactArgs(2), Args: require.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if o.version == "" && o.devel { client.Namespace = getNamespace()
debug("setting version to >0.0.0-0")
o.version = ">0.0.0-0"
}
o.release = args[0] if client.Version == "" && client.Devel {
o.chart = args[1] debug("setting version to >0.0.0-0")
o.client = ensureHelmClient(o.client, false) client.Version = ">0.0.0-0"
return o.run(out)
},
} }
f := cmd.Flags() if err := client.ValueOptions.MergeValues(settings); err != nil {
f.BoolVar(&o.dryRun, "dry-run", false, "simulate an upgrade") return err
f.BoolVar(&o.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&o.force, "force", false, "force resource update through delete/recreate if needed")
f.BoolVar(&o.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks. DEPRECATED. Use no-hooks")
f.BoolVar(&o.disableHooks, "no-hooks", false, "disable pre/post upgrade hooks")
f.BoolVarP(&o.install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
f.Int64Var(&o.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&o.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart")
f.BoolVar(&o.reuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored.")
f.BoolVar(&o.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&o.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.IntVar(&o.maxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.")
o.valuesOptions.addFlags(f)
o.chartPathOptions.addFlags(f)
return cmd
} }
func (o *upgradeOptions) run(out io.Writer) error { chartPath, err := client.ChartPathOptions.LocateChart(args[1], settings)
chartPath, err := o.locateChart(o.chart)
if err != nil { if err != nil {
return err return err
} }
if o.install { if client.Install {
// If a release does not exist, install it. If another error occurs during // If a release does not exist, install it. If another error occurs during
// the check, ignore the error and continue with the upgrade. // the check, ignore the error and continue with the upgrade.
if _, err := o.client.ReleaseHistory(o.release, 1); err == driver.ErrReleaseNotFound { histClient := action.NewHistory(cfg)
fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", o.release) histClient.Max = 1
io := &installOptions{ if _, err := histClient.Run(args[0]); err == driver.ErrReleaseNotFound {
chartPath: chartPath, fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0])
client: o.client, instClient := action.NewInstall(cfg)
name: o.release, instClient.ChartPathOptions = client.ChartPathOptions
dryRun: o.dryRun, instClient.ValueOptions = client.ValueOptions
disableHooks: o.disableHooks, instClient.DryRun = client.DryRun
timeout: o.timeout, instClient.DisableHooks = client.DisableHooks
wait: o.wait, instClient.Timeout = client.Timeout
valuesOptions: o.valuesOptions, instClient.Wait = client.Wait
chartPathOptions: o.chartPathOptions, instClient.Devel = client.Devel
} instClient.Namespace = client.Namespace
return io.run(out)
} _, err := runInstall(args, instClient, out)
}
rawVals, err := o.mergedValues()
if err != nil {
return err return err
} }
}
// Check chart dependencies to make sure all are present in /charts // Check chart dependencies to make sure all are present in /charts
ch, err := loader.Load(chartPath) ch, err := loader.Load(chartPath)
@ -154,33 +107,48 @@ func (o *upgradeOptions) run(out io.Writer) error {
return err return err
} }
if req := ch.Metadata.Dependencies; req != nil { if req := ch.Metadata.Dependencies; req != nil {
if err := checkDependencies(ch, req); err != nil { if err := action.CheckDependencies(ch, req); err != nil {
return err return err
} }
} }
resp, err := o.client.UpdateRelease( resp, err := client.Run(args[0], ch)
o.release,
chartPath,
helm.UpdateValueOverrides(rawVals),
helm.UpgradeDryRun(o.dryRun),
helm.UpgradeRecreate(o.recreate),
helm.UpgradeForce(o.force),
helm.UpgradeDisableHooks(o.disableHooks),
helm.UpgradeTimeout(o.timeout),
helm.ResetValues(o.resetValues),
helm.ReuseValues(o.reuseValues),
helm.UpgradeWait(o.wait),
helm.MaxHistory(o.maxHistory))
if err != nil { if err != nil {
return errors.Wrap(err, "UPGRADE FAILED") return errors.Wrap(err, "UPGRADE FAILED")
} }
if settings.Debug { if settings.Debug {
printRelease(out, resp) action.PrintRelease(out, resp)
}
fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", args[0])
// Print the status like status command does
statusClient := action.NewStatus(cfg)
rel, err := statusClient.Run(args[0])
if err != nil {
return err
} }
action.PrintRelease(out, rel)
fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", o.release)
PrintStatus(out, resp)
return nil return nil
},
}
f := cmd.Flags()
f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
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.DryRun, "dry-run", false, "simulate an upgrade")
f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&client.Force, "force", false, "force resource update through delete/recreate if needed")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks")
f.Int64Var(&client.Timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart")
f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored.")
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.IntVar(&client.MaxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.")
addChartPathOptionsFlags(f, &client.ChartPathOptions)
addValueOptionsFlags(f, &client.ValueOptions)
return cmd
} }

@ -23,8 +23,7 @@ import (
"k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader" "k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/release"
"k8s.io/helm/pkg/helm"
) )
func TestUpgradeCmd(t *testing.T) { func TestUpgradeCmd(t *testing.T) {
@ -42,7 +41,7 @@ func TestUpgradeCmd(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Error loading chart: %v", err) t.Fatalf("Error loading chart: %v", err)
} }
_ = helm.ReleaseMock(&helm.MockReleaseOptions{ _ = release.Mock(&release.MockReleaseOptions{
Name: "funny-bunny", Name: "funny-bunny",
Chart: ch, Chart: ch,
}) })
@ -84,7 +83,7 @@ func TestUpgradeCmd(t *testing.T) {
badDepsPath := "testdata/testcharts/chart-bad-requirements" badDepsPath := "testdata/testcharts/chart-bad-requirements"
relMock := func(n string, v int, ch *chart.Chart) *release.Release { relMock := func(n string, v int, ch *chart.Chart) *release.Release {
return helm.ReleaseMock(&helm.MockReleaseOptions{Name: n, Version: v, Chart: ch}) return release.Mock(&release.MockReleaseOptions{Name: n, Version: v, Chart: ch})
} }
tests := []cmdTestCase{ tests := []cmdTestCase{

@ -21,7 +21,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require" "k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/downloader" "k8s.io/helm/pkg/action"
) )
const verifyDesc = ` const verifyDesc = `
@ -35,13 +35,8 @@ This command can be used to verify a local chart. Several other commands provide
the 'helm package --sign' command. the 'helm package --sign' command.
` `
type verifyOptions struct {
keyring string
chartfile string
}
func newVerifyCmd(out io.Writer) *cobra.Command { func newVerifyCmd(out io.Writer) *cobra.Command {
o := &verifyOptions{} client := action.NewVerify()
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "verify PATH", Use: "verify PATH",
@ -49,18 +44,11 @@ func newVerifyCmd(out io.Writer) *cobra.Command {
Long: verifyDesc, Long: verifyDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
o.chartfile = args[0] return client.Run(args[0])
return o.run(out)
}, },
} }
f := cmd.Flags() cmd.Flags().StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.StringVar(&o.keyring, "keyring", defaultKeyring(), "keyring containing public keys")
return cmd return cmd
} }
func (o *verifyOptions) run(out io.Writer) error {
_, err := downloader.VerifyChart(o.chartfile, o.keyring)
return err
}

@ -72,7 +72,7 @@ func TestVerifyCmd(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
out, err := executeCommand(nil, tt.cmd) _, out, err := executeActionCommand(tt.cmd)
if tt.wantError { if tt.wantError {
if err == nil { if err == nil {
t.Errorf("Expected error, but got none: %q", out) t.Errorf("Expected error, but got none: %q", out)

@ -10,11 +10,11 @@ Helm can be installed either from source, or from pre-built binary releases.
### From the Binary Releases ### From the Binary Releases
Every [release](https://github.com/helm/helm/releases) of Helm Every [release](https://github.com/helm/releases) of Helm
provides binary releases for a variety of OSes. These binary versions provides binary releases for a variety of OSes. These binary versions
can be manually downloaded and installed. can be manually downloaded and installed.
1. Download your [desired version](https://github.com/helm/helm/releases) 1. Download your [desired version](https://github.com/helm/releases)
2. Unpack it (`tar -zxvf helm-v2.0.0-linux-amd64.tgz`) 2. Unpack it (`tar -zxvf helm-v2.0.0-linux-amd64.tgz`)
3. Find the `helm` binary in the unpacked directory, and move it to its 3. Find the `helm` binary in the unpacked directory, and move it to its
desired destination (`mv linux-amd64/helm /usr/local/bin/helm`) desired destination (`mv linux-amd64/helm /usr/local/bin/helm`)

@ -75,7 +75,7 @@ func compare(actual []byte, filename string) error {
return errors.Wrapf(err, "unable to read testdata %s", filename) return errors.Wrapf(err, "unable to read testdata %s", filename)
} }
if !bytes.Equal(expected, actual) { if !bytes.Equal(expected, actual) {
return errors.Errorf("does not match golden file %s\n\nWANT:\n%s\n\nGOT:\n%s\n", filename, expected, actual) return errors.Errorf("does not match golden file %s\n\nWANT:\n'%s'\n\nGOT:\n'%s'\n", filename, expected, actual)
} }
return nil return nil
} }

@ -17,15 +17,18 @@ limitations under the License.
package action package action
import ( import (
"regexp"
"time" "time"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery" "k8s.io/client-go/discovery"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/registry" "k8s.io/helm/pkg/registry"
"k8s.io/helm/pkg/release"
"k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/tiller/environment"
) )
// Timestamper is a function capable of producing a timestamp.Timestamper. // Timestamper is a function capable of producing a timestamp.Timestamper.
@ -34,6 +37,28 @@ import (
// though, so that timestamps are predictable. // though, so that timestamps are predictable.
var Timestamper = time.Now var Timestamper = time.Now
var (
// errMissingChart indicates that a chart was not provided.
errMissingChart = errors.New("no chart provided")
// errMissingRelease indicates that a release (name) was not provided.
errMissingRelease = errors.New("no release provided")
// errInvalidRevision indicates that an invalid release revision number was provided.
errInvalidRevision = errors.New("invalid release revision")
//errInvalidName indicates that an invalid release name was provided
errInvalidName = errors.New("invalid release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 53")
)
// ValidName is a regular expression for names.
//
// According to the Kubernetes help text, the regular expression it uses is:
//
// (([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?
//
// We modified that. First, we added start and end delimiters. Second, we changed
// the final ? to + to require that the pattern match at least once. This modification
// prevents an empty string from matching.
var ValidName = regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$")
// Configuration injects the dependencies that all actions share. // Configuration injects the dependencies that all actions share.
type Configuration struct { type Configuration struct {
// Discovery contains a discovery client // Discovery contains a discovery client
@ -43,7 +68,7 @@ type Configuration struct {
Releases *storage.Storage Releases *storage.Storage
// KubeClient is a Kubernetes API client. // KubeClient is a Kubernetes API client.
KubeClient environment.KubeClient KubeClient kube.KubernetesClient
// RegistryClient is a client for working with registries // RegistryClient is a client for working with registries
RegistryClient *registry.Client RegistryClient *registry.Client
@ -69,6 +94,18 @@ func (c *Configuration) Now() time.Time {
return Timestamper() return Timestamper()
} }
func (c *Configuration) releaseContent(name string, version int) (*release.Release, error) {
if err := validateReleaseName(name); err != nil {
return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name)
}
if version <= 0 {
return c.Releases.Last(name)
}
return c.Releases.Get(name, version)
}
// GetVersionSet retrieves a set of available k8s API versions // GetVersionSet retrieves a set of available k8s API versions
func GetVersionSet(client discovery.ServerGroupsInterface) (chartutil.VersionSet, error) { func GetVersionSet(client discovery.ServerGroupsInterface) (chartutil.VersionSet, error) {
groups, err := client.ServerGroups() groups, err := client.ServerGroups()
@ -87,3 +124,14 @@ func GetVersionSet(client discovery.ServerGroupsInterface) (chartutil.VersionSet
versions := metav1.ExtractGroupVersions(groups) versions := metav1.ExtractGroupVersions(groups)
return chartutil.NewVersionSet(versions...), nil return chartutil.NewVersionSet(versions...), nil
} }
// recordRelease with an update operation in case reuse has been set.
func (c *Configuration) recordRelease(r *release.Release, reuse bool) {
if reuse {
if err := c.Releases.Update(r); err != nil {
c.Log("warning: Failed to update release %s: %s", r.Name, err)
}
} else if err := c.Releases.Create(r); err != nil {
c.Log("warning: Failed to record release %s: %s", r.Name, err)
}
}

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

Loading…
Cancel
Save