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),
} }
return o.run(out) if client.Verify {
man.Verify = downloader.VerifyIfPossible
}
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 { return filepath.Join(homedir.HomeDir(), ".gnupg", "pubring.gpg")
man.Verify = downloader.VerifyIfPossible
}
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])
}
man := &downloader.Manager{
Out: out,
ChartPath: chartpath,
HelmHome: settings.Home,
Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings),
}
if client.Verify {
man.Verify = downloader.VerifyAlways
} }
o.helmhome = settings.Home if settings.Debug {
return o.run(out) man.Debug = true
}
return man.Update()
}, },
} }
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")
f.BoolVar(&o.skipRefresh, "skip-refresh", false, "do not refresh the local repository cache") f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
return cmd return cmd
} }
// run runs the full dependency update process.
func (o *dependencyUpdateOptions) run(out io.Writer) error {
man := &downloader.Manager{
Out: out,
ChartPath: o.chartpath,
HelmHome: o.helmhome,
Keyring: o.keyring,
SkipUpdate: o.skipRefresh,
Getters: getter.All(settings),
}
if o.verify {
man.Verify = downloader.VerifyAlways
}
if settings.Debug {
man.Debug = true
}
return man.Update()
}

@ -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) if err != nil {
return o.run(out) return err
}
for _, hook := range res.Hooks {
fmt.Fprintf(out, "---\n# %s\n%s", hook.Name, hook.Manifest)
}
return nil
}, },
} }
cmd.Flags().IntVar(&o.version, "revision", 0, "get the named release with revision")
return cmd
}
func (o *getHooksOptions) run(out io.Writer) error { cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
res, err := o.client.ReleaseContent(o.release, o.version)
if err != nil {
fmt.Fprintln(out, o.release)
return err
}
for _, hook := range res.Hooks { return cmd
fmt.Fprintf(out, "---\n# %s\n%s", hook.Name, hook.Manifest)
}
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 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) if err != nil {
return o.run(out) return err
}
fmt.Fprintln(out, res.Manifest)
return nil
}, },
} }
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")
return cmd
}
// getManifest implements 'helm get manifest' return cmd
func (o *getManifestOptions) run(out io.Writer) error {
res, err := o.client.ReleaseContent(o.release, o.version)
if err != nil {
return err
}
fmt.Fprintln(out, res.Manifest)
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 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) if err != nil {
return o.run(out) return err
}
fmt.Fprintln(out, res)
return nil
}, },
} }
cmd.Flags().BoolVarP(&o.allValues, "all", "a", false, "dump all (computed) values") f := cmd.Flags()
cmd.Flags().IntVar(&o.version, "revision", 0, "get the named release with revision") f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
f.BoolVarP(&client.AllValues, "all", "a", false, "dump all (computed) values")
return cmd 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 {
return err
}
fmt.Fprintln(out, cfgStr)
return nil
}
resConfig, err := yaml.Marshal(res.Config)
if err != nil {
return err
}
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)
@ -136,35 +137,11 @@ type cmdTestCase struct {
golden string golden string
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 executeActionCommand(cmd string) (*cobra.Command, string, error) {
func executeCommandC(client helm.Interface, cmd string) (*cobra.Command, string, error) { return executeActionCommandC(storageFixture(), cmd)
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()
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] if err != nil {
return o.run(out) return err
}
fmt.Fprintln(out, history)
return nil
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.IntVar(&o.max, "max", 256, "maximum number of revision to include in history") f.StringVarP(&client.OutputFormat, "output", "o", action.Table.String(), "prints the output in the specified format (json|table|yaml)")
f.UintVar(&o.colWidth, "col-width", 60, "specifies the max column width of output") f.IntVar(&client.Max, "max", 256, "maximum number of revision to include in history")
f.StringVarP(&o.outputFormat, "output", "o", "table", "prints the output in the specified format (json|table|yaml)")
return cmd return cmd
} }
func (o *historyOptions) run(out io.Writer) error {
rels, err := o.client.ReleaseHistory(o.release, o.max)
if err != nil {
return err
}
if len(rels) == 0 {
return nil
}
releaseHistory := getReleaseHistory(rels)
var history []byte
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 {
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 { if err != nil {
return err return err
} }
o.name = name // FIXME action.PrintRelease(out, rel)
return nil
cp, err := o.locateChart(chart)
if err != nil {
return err
}
o.chartPath = cp
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()
}
if o.nameTemplate != "" { func addValueOptionsFlags(f *pflag.FlagSet, v *action.ValueOptions) {
newName, err := templateName(o.nameTemplate) f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL(can specify multiple)")
return newName, args[0], err f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
} 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)")
}
if !o.generateName { func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
return "", args[0], errors.New("must either provide a name or specify --generate-name") f.StringVar(&c.Version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed")
} f.BoolVar(&c.Verify, "verify", false, "verify the package before installing it")
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")
f.StringVar(&c.Password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&c.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
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")
}
base := filepath.Base(args[0]) func runInstall(args []string, client *action.Install, out io.Writer) (*release.Release, error) {
if base == "." || base == "" { debug("Original chart version: %q", client.Version)
base = "chart" if client.Version == "" && client.Devel {
debug("setting version to >0.0.0-0")
client.Version = ">0.0.0-0"
} }
newName := fmt.Sprintf("%s-%d", base, time.Now().Unix())
return newName, args[0], nil
}
func (o *installOptions) run(out io.Writer) error { name, chart, err := client.NameAndChart(args)
debug("CHART PATH: %s\n", o.chartPath) if err != nil {
return nil, err
}
client.ReleaseName = name
rawVals, err := o.mergedValues() cp, err := client.ChartPathOptions.LocateChart(chart, settings)
if err != nil { if err != nil {
return err return nil, err
} }
// If template is specified, try to run the template. debug("CHART PATH: %s\n", cp)
if o.nameTemplate != "" {
o.name, err = templateName(o.nameTemplate) if err := client.ValueOptions.MergeValues(settings); err != nil {
if err != nil { return nil, err
return 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)
} }
// 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
func mergeValues(dest, src map[string]interface{}) map[string]interface{} {
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 { client.Namespace = getNamespace()
return errors.Errorf("found in Chart.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", ")) return client.Run(chartRequested)
}
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 {
} return err
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
}
var total int
var failures int
for _, path := range o.paths {
if linter, err := lintChart(path, rvals, getNamespace(), o.strict); err != nil {
fmt.Println("==> Skipping", path)
fmt.Println(err)
if err == errLintNoChart {
failures = failures + 1
} }
} else { result := client.Run(paths)
fmt.Println("==> Linting", path) var message strings.Builder
fmt.Fprintf(&message, "%d chart(s) linted, %d chart(s) failed\n", result.TotalChartsLinted, len(result.Errors))
if len(linter.Messages) == 0 { for _, err := range result.Errors {
fmt.Println("Lint OK") fmt.Fprintf(&message, "\t%s\n", err)
} }
for _, msg := range result.Messages {
for _, msg := range linter.Messages { fmt.Fprintf(&message, "\t%s\n", msg)
fmt.Println(msg)
} }
total = total + 1 if len(result.Errors) > 0 {
if linter.HighestSeverity >= lowestTolerance { return errors.New(message.String())
failures = failures + 1
} }
} fmt.Fprintf(out, message.String())
fmt.Println("") return nil
} },
msg := fmt.Sprintf("%d chart(s) linted", total)
if failures > 0 {
return errors.Errorf("%s, %d chart(s) failed", msg, failures)
}
fmt.Fprintf(out, "%s, no failures\n", msg)
return nil
}
func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) {
var chartPath string
linter := support.Linter{}
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 f := cmd.Flags()
for _, value := range o.values { f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings")
if err := strvals.ParseInto(value, base); err != nil { addValueOptionsFlags(f, &client.ValueOptions)
return base, errors.Wrap(err, "failed parsing --set data")
}
}
// User specified a value via --set-string return cmd
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
} }

@ -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)
}
lister := action.NewList(actionConfig)
lister.All = o.limit == -1
lister.AllNamespaces = o.allNamespaces
lister.Limit = o.limit
lister.Offset = o.offset
lister.Filter = o.filter
// Set StateMask
lister.StateMask = o.setStateMask()
// Set sorter
lister.Sort = action.ByNameAsc
if o.sortDesc {
lister.Sort = action.ByNameDesc
}
if o.byDate {
lister.Sort = action.ByDate
} }
client.All = client.Limit == -1
client.SetStateMask()
results, err := lister.Run() results, err := client.Run()
if o.short { if client.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,186 +42,68 @@ 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")
} }
} }
if err := client.ValueOptions.MergeValues(settings); err != nil {
return err
}
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
o.path = args[i] path, err := filepath.Abs(args[i])
if err := o.run(out); err != nil { if err != nil {
return err
}
if client.DependencyUpdate {
downloadManager := &downloader.Manager{
Out: ioutil.Discard,
ChartPath: path,
HelmHome: settings.Home,
Keyring: client.Keyring,
Getters: getter.All(settings),
Debug: settings.Debug,
}
if err := downloadManager.Update(); err != nil {
return err
}
}
p, err := client.Run(path)
if err != nil {
return err return err
} }
fmt.Fprintf(out, "Successfully packaged chart and saved it to: %s\n", p)
} }
return nil return nil
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&o.sign, "sign", false, "use a PGP private key to sign this package") f.BoolVar(&client.Sign, "sign", false, "use a PGP private key to sign this package")
f.StringVar(&o.key, "key", "", "name of the key to use when signing. Used if --sign is true") f.StringVar(&client.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(&client.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(&client.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.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version")
f.StringVarP(&o.destination, "destination", "d", ".", "location to write the chart.") f.StringVarP(&client.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`) f.BoolVarP(&client.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`)
o.valuesOptions.addFlags(f) addValueOptionsFlags(f, &client.ValueOptions)
return cmd return cmd
} }
func (o *packageOptions) run(out io.Writer) error {
path, err := filepath.Abs(o.path)
if err != nil {
return err
}
if o.dependencyUpdate {
downloadManager := &downloader.Manager{
Out: out,
ChartPath: path,
HelmHome: settings.Home,
Keyring: o.keyring,
Getters: getter.All(settings),
Debug: settings.Debug,
}
if err := downloadManager.Update(); err != nil {
return err
}
}
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 {
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
}
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
}
func (o *packageOptions) clearsign(filename string) error {
// Load keyring
signer, err := provenance.NewFromKeyring(o.keyring, o.key)
if err != nil {
return err
}
if err := signer.DecryptKey(promptUser); err != nil {
return err
}
sig, err := signer.ClearSign(filename)
if err != nil {
return err
}
debug(sig)
return ioutil.WriteFile(filename+".prov", []byte(sig), 0755)
}
// promptUser implements provenance.PassphraseFetcher
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,47 +44,37 @@ 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) testErr := &testErr{}
return o.run(out)
for {
select {
case err := <-errc:
if err == nil && testErr.failed > 0 {
return testErr.Error()
}
return err
case res, ok := <-c:
if !ok {
break
}
if res.Status == release.TestRunFailure {
testErr.failed++
}
fmt.Fprintf(out, res.Msg+"\n")
}
}
}, },
} }
f := cmd.Flags() f := cmd.Flags()
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)")
f.BoolVar(&o.cleanup, "cleanup", false, "delete test pods upon completion") f.BoolVar(&client.Cleanup, "cleanup", false, "delete test pods upon completion")
return cmd 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{}
for {
select {
case err := <-errc:
if err == nil && testErr.failed > 0 {
return testErr.Error()
}
return err
case res, ok := <-c:
if !ok {
break
}
if res.Status == release.TestRunFailure {
testErr.failed++
}
fmt.Fprintf(out, res.Msg+"\n")
}
}
}
type testErr struct { type testErr struct {
failed int failed int
} }

@ -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 failure", {
cmd: "test example-fail", Name: "test-success",
testRunStatus: map[string]release.TestRunStatus{"FAILURE: red lights everywhere": release.TestRunFailure}, Status: release.TestRunSuccess,
wantError: true, StartedAt: timestamp,
golden: "output/test-failure.txt", CompletedAt: timestamp,
},
},
})},
golden: "output/test-success.txt",
}, { }, {
name: "test unknown", name: "test failure",
cmd: "test example-unknown", cmd: "status test-failure",
testRunStatus: map[string]release.TestRunStatus{"UNKNOWN: yellow lights everywhere": release.TestRunUnknown}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{
golden: "output/test-unknown.txt", Name: "test-failure",
TestSuiteResults: []*release.TestRun{
{
Name: "test-failure",
Status: release.TestRunFailure,
StartedAt: timestamp,
CompletedAt: timestamp,
},
},
})},
golden: "output/test-failure.txt",
}, { }, {
name: "test error", name: "test unknown",
cmd: "test example-error", cmd: "status test-unknown",
testRunStatus: map[string]release.TestRunStatus{"ERROR: yellow lights everywhere": release.TestRunFailure}, rels: []*release.Release{release.Mock(&release.MockReleaseOptions{
wantError: true, Name: "test-unknown",
golden: "output/test-error.txt", TestSuiteResults: []*release.TestRun{
{
Name: "test-unknown",
Status: release.TestRunUnknown,
StartedAt: timestamp,
CompletedAt: timestamp,
},
},
})},
golden: "output/test-unknown.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{
golden: "output/test-running.txt", Name: "test-running",
TestSuiteResults: []*release.TestRun{
{
Name: "test-running",
Status: release.TestRunRunning,
StartedAt: timestamp,
CompletedAt: timestamp,
},
},
})},
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 { 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
}, },
} }
@ -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 { 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
}, },
} }
@ -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 { 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
}, },
} }
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
}
outfmt, err := action.ParseOutputFormat(client.OutputFormat)
// We treat an invalid format type as the default
if err != nil && err != action.ErrInvalidFormatType {
return err
}
switch outfmt {
case "":
action.PrintRelease(out, rel)
return nil
case action.JSON:
data, err := json.Marshal(rel)
if err != nil {
return errors.Wrap(err, "failed to Marshal JSON output")
}
out.Write(data)
return nil
case action.YAML:
data, err := yaml.Marshal(rel)
if err != nil {
return errors.Wrap(err, "failed to Marshal YAML output")
}
out.Write(data)
return nil
default:
return errors.Errorf("unknown output format %q", outfmt)
}
}, },
} }
cmd.PersistentFlags().IntVar(&o.version, "revision", 0, "if set, display the status of the named release with revision") f := cmd.PersistentFlags()
cmd.PersistentFlags().StringVarP(&o.outfmt, "output", "o", "", "output the status in the specified format (json or yaml)") 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)")
return cmd return cmd
} }
func (o *statusOptions) run(out io.Writer) error {
res, err := o.client.ReleaseContent(o.release, o.version)
if err != nil {
return err
}
switch o.outfmt {
case "":
PrintStatus(out, res)
return nil
case "json":
data, err := json.Marshal(res)
if err != nil {
return errors.Wrap(err, "failed to Marshal JSON output")
}
out.Write(data)
return nil
case "yaml":
data, err := yaml.Marshal(res)
if err != nil {
return errors.Wrap(err, "failed to Marshal YAML output")
}
out.Write(data)
return nil
}
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 {
fmt.Fprintf(out, "NOTES:\n%s\n", res.Info.Notes)
}
}
func formatTestResults(results []*release.TestRun) string {
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,15 +20,18 @@ 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) {
releasesMockWithStatus := func(info *release.Info) []*release.Release { releasesMockWithStatus := func(info *release.Info) []*release.Release {
info.LastDeployed = time.Unix(1452902400, 0).UTC() info.LastDeployed = time.Unix(1452902400, 0).UTC()
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 {
nameTemplate string // --name-template
showNotes bool // --notes
releaseName string // --name
renderFiles []string // --execute
kubeVersion string // --kube-version
outputDir string // --output-dir
valuesOptions
chartPath string
}
func newTemplateCmd(out io.Writer) *cobra.Command { func newTemplateCmd(out io.Writer) *cobra.Command {
o := &templateOptions{} customConfig := &action.Configuration{
// Add mock objects in here so it doesn't use Kube API server
Releases: storage.Init(driver.NewMemory()),
KubeClient: &kube.PrintingKubeClient{Out: ioutil.Discard},
Discovery: fake.NewSimpleClientset().Discovery(),
Log: func(format string, v ...interface{}) {
fmt.Fprintf(out, format, v...)
},
}
client := action.NewInstall(customConfig)
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)
} if err != nil {
} else {
return err return err
} }
return o.run(out) fmt.Fprintln(out, strings.TrimSpace(rel.Manifest))
return nil
}, },
} }
f := cmd.Flags() addInstallFlags(cmd.Flags(), client)
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 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 {
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
}
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
}
// check if the directory exists to create file. creates if don't exists
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)
}

@ -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,4 @@
NAME: aeneas NAME: aeneas
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

@ -1,5 +1,4 @@
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
STATUS: deployed STATUS: deployed

@ -1,4 +1,4 @@
NAME: aeneas NAME: aeneas
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

@ -1,4 +1,4 @@
NAME: virgil NAME: virgil
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

@ -1,4 +1,4 @@
NAME: virgil NAME: virgil
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

@ -1,4 +1,4 @@
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
STATUS: deployed STATUS: deployed

@ -1,4 +1,4 @@
NAME: virgil NAME: virgil
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

@ -1,4 +1,4 @@
NAME: virgil NAME: virgil
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

@ -1,4 +1,4 @@
NAME: apollo NAME: apollo
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

@ -1,4 +1,4 @@
NAME: aeneas NAME: aeneas
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

@ -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

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

Loading…
Cancel
Save