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"
// 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)
return
}
@ -86,7 +86,7 @@ func TestCreateStarterCmd(t *testing.T) {
defer testChdir(t, tdir)()
// 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)
return
}

@ -16,18 +16,13 @@ limitations under the License.
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/Masterminds/semver"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/action"
)
const dependencyDesc = `
@ -103,14 +98,8 @@ func newDependencyCmd(out io.Writer) *cobra.Command {
return cmd
}
type dependencyLisOptions struct {
chartpath string
}
func newDependencyListCmd(out io.Writer) *cobra.Command {
o := &dependencyLisOptions{
chartpath: ".",
}
client := action.NewDependency()
cmd := &cobra.Command{
Use: "list CHART",
@ -119,151 +108,12 @@ func newDependencyListCmd(out io.Writer) *cobra.Command {
Long: dependencyListDesc,
Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
chartpath := "."
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
}
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 (
"io"
"os"
"path/filepath"
"github.com/spf13/cobra"
"k8s.io/client-go/util/homedir"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/downloader"
"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'.
`
type dependencyBuildOptions struct {
keyring string // --keyring
verify bool // --verify
chartpath string
}
func newDependencyBuildCmd(out io.Writer) *cobra.Command {
o := &dependencyBuildOptions{
chartpath: ".",
}
client := action.NewDependency()
cmd := &cobra.Command{
Use: "build CHART",
@ -54,31 +49,38 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
Long: dependencyBuildDesc,
Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
chartpath := "."
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.BoolVar(&o.verify, "verify", false, "verify the packages against signatures")
f.StringVar(&o.keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures")
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
return cmd
}
func (o *dependencyBuildOptions) run(out io.Writer) error {
man := &downloader.Manager{
Out: out,
ChartPath: o.chartpath,
HelmHome: settings.Home,
Keyring: o.keyring,
Getters: getter.All(settings),
// 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")
}
if o.verify {
man.Verify = downloader.VerifyIfPossible
}
return man.Build()
return filepath.Join(homedir.HomeDir(), ".gnupg", "pubring.gpg")
}

@ -44,7 +44,7 @@ func TestDependencyBuildCmd(t *testing.T) {
}
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.
if err != nil {
@ -72,7 +72,7 @@ func TestDependencyBuildCmd(t *testing.T) {
t.Fatal(err)
}
out, err = executeCommand(nil, cmd)
_, out, err = executeActionCommand(cmd)
if err != nil {
t.Logf("Output: %s", out)
t.Fatal(err)

@ -22,9 +22,9 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/downloader"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm/helmpath"
)
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.
`
// 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.
func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
o := &dependencyUpdateOptions{
chartpath: ".",
}
client := action.NewDependency()
cmd := &cobra.Command{
Use: "update CHART",
@ -67,37 +53,32 @@ func newDependencyUpdateCmd(out io.Writer) *cobra.Command {
Long: dependencyUpDesc,
Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
chartpath := "."
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
return o.run(out)
if settings.Debug {
man.Debug = true
}
return man.Update()
},
}
f := cmd.Flags()
f.BoolVar(&o.verify, "verify", false, "verify the packages against signatures")
f.StringVar(&o.keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&o.skipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures")
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
return cmd
}
// 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
import (
"bytes"
"fmt"
"io/ioutil"
"os"
@ -52,7 +51,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
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 {
t.Logf("Output: %s", out)
t.Fatal(err)
@ -95,7 +94,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
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 {
t.Logf("Output: %s", out)
t.Fatal(err)
@ -133,7 +132,7 @@ func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) {
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 {
t.Fatal("Expected failure to find the repo with skipRefresh")
}
@ -164,13 +163,8 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
t.Fatal(err)
}
out := bytes.NewBuffer(nil)
o := &dependencyUpdateOptions{}
o.helmhome = hh
o.chartpath = hh.Path(chartname)
if err := o.run(out); err != nil {
output := out.String()
_, output, err := executeActionCommand(fmt.Sprintf("--home='%s' dependency update %s", hh, hh.Path(chartname)))
if err != nil {
t.Logf("Output: %s", output)
t.Fatal(err)
}
@ -178,14 +172,14 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
// Chart repo is down
srv.Stop()
if err := o.run(out); err == nil {
output := out.String()
_, output, err = executeActionCommand(fmt.Sprintf("--home='%s' dependency update %s", hh, hh.Path(chartname)))
if err == nil {
t.Logf("Output: %s", output)
t.Fatal("Expected error, got nil")
}
// 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 {
t.Fatal(err)
}
@ -201,7 +195,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
}
// 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")
}
}

@ -22,7 +22,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/action"
)
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.
`
type getOptions struct {
version int // --revision
release string
client helm.Interface
}
func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
o := &getOptions{client: client}
func newGetCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewGet(cfg)
cmd := &cobra.Command{
Use: "get RELEASE_NAME",
@ -55,25 +47,19 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
Long: getHelp,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
o.release = args[0]
o.client = ensureHelmClient(o.client, false)
return o.run(out)
res, err := client.Run(args[0])
if err != nil {
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(newGetManifestCmd(client, out))
cmd.AddCommand(newGetHooksCmd(client, out))
cmd.AddCommand(newGetValuesCmd(cfg, out))
cmd.AddCommand(newGetManifestCmd(cfg, out))
cmd.AddCommand(newGetHooksCmd(cfg, out))
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"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/action"
)
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.
`
type getHooksOptions struct {
release string
client helm.Interface
version int
}
func newGetHooksCmd(client helm.Interface, out io.Writer) *cobra.Command {
o := &getHooksOptions{client: client}
func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewGet(cfg)
cmd := &cobra.Command{
Use: "hooks RELEASE_NAME",
@ -47,24 +41,18 @@ func newGetHooksCmd(client helm.Interface, out io.Writer) *cobra.Command {
Long: getHooksHelp,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
o.release = args[0]
o.client = ensureHelmClient(o.client, false)
return o.run(out)
res, err := client.Run(args[0])
if err != nil {
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 {
res, err := o.client.ReleaseContent(o.release, o.version)
if err != nil {
fmt.Fprintln(out, o.release)
return err
}
cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
for _, hook := range res.Hooks {
fmt.Fprintf(out, "---\n# %s\n%s", hook.Name, hook.Manifest)
}
return nil
return cmd
}

@ -19,8 +19,7 @@ package main
import (
"testing"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/release"
)
func TestGetHooks(t *testing.T) {
@ -28,7 +27,7 @@ func TestGetHooks(t *testing.T) {
name: "get hooks with release",
cmd: "get hooks aeneas",
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",
cmd: "get hooks",

@ -23,7 +23,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/action"
)
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.
`
type getManifestOptions struct {
version int // --revision
release string
client helm.Interface
}
func newGetManifestCmd(client helm.Interface, out io.Writer) *cobra.Command {
o := &getManifestOptions{client: client}
func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewGet(cfg)
cmd := &cobra.Command{
Use: "manifest RELEASE_NAME",
@ -51,22 +43,16 @@ func newGetManifestCmd(client helm.Interface, out io.Writer) *cobra.Command {
Long: getManifestHelp,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
o.release = args[0]
o.client = ensureHelmClient(o.client, false)
return o.run(out)
res, err := client.Run(args[0])
if err != nil {
return err
}
fmt.Fprintln(out, res.Manifest)
return nil
},
}
cmd.Flags().IntVar(&o.version, "revision", 0, "get the named release with revision")
return cmd
}
cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
// getManifest implements 'helm get manifest'
func (o *getManifestOptions) run(out io.Writer) error {
res, err := o.client.ReleaseContent(o.release, o.version)
if err != nil {
return err
}
fmt.Fprintln(out, res.Manifest)
return nil
return cmd
}

@ -19,8 +19,7 @@ package main
import (
"testing"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/release"
)
func TestGetManifest(t *testing.T) {
@ -28,7 +27,7 @@ func TestGetManifest(t *testing.T) {
name: "get manifest with release",
cmd: "get manifest juno",
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",
cmd: "get manifest",

@ -19,8 +19,7 @@ package main
import (
"testing"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/release"
)
func TestGetCmd(t *testing.T) {
@ -28,7 +27,7 @@ func TestGetCmd(t *testing.T) {
name: "get with a release",
cmd: "get thomas-guide",
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",
cmd: "get",

@ -20,29 +20,18 @@ import (
"fmt"
"io"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/action"
)
var getValuesHelp = `
This command downloads a values file for a given release.
`
type getValuesOptions struct {
allValues bool // --all
version int // --revision
release string
client helm.Interface
}
func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command {
o := &getValuesOptions{client: client}
func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewGetValues(cfg)
cmd := &cobra.Command{
Use: "values RELEASE_NAME",
@ -50,43 +39,17 @@ func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command {
Long: getValuesHelp,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
o.release = args[0]
o.client = ensureHelmClient(o.client, false)
return o.run(out)
res, err := client.Run(args[0])
if err != nil {
return err
}
fmt.Fprintln(out, res)
return nil
},
}
cmd.Flags().BoolVarP(&o.allValues, "all", "a", false, "dump all (computed) values")
cmd.Flags().IntVar(&o.version, "revision", 0, "get the named release with revision")
f := cmd.Flags()
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
}
// 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 (
"testing"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/release"
)
func TestGetValuesCmd(t *testing.T) {
@ -28,7 +27,7 @@ func TestGetValuesCmd(t *testing.T) {
name: "get values with a release",
cmd: "get values thomas-guide",
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",
cmd: "get values",

@ -27,15 +27,14 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/cli"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"
)
var (
settings environment.EnvSettings
settings cli.EnvSettings
config genericclioptions.RESTClientGetter
configOnce sync.Once
)
@ -52,45 +51,13 @@ func logf(format string, v ...interface{}) {
}
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 {
logf("%+v", err)
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 {
kc := kube.New(kubeConfig())
kc.Log = logf

@ -30,13 +30,12 @@ import (
"k8s.io/helm/internal/test"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/helmpath"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/release"
"k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"
"k8s.io/helm/pkg/tiller/environment"
)
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) {
defer resetEnv()()
c := &helm.FakeClient{
Rels: tt.rels,
TestRunStatus: tt.testRunStatus,
storage := storageFixture()
for _, rel := range tt.rels {
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 {
t.Errorf("expected error, got '%v'", err)
}
@ -115,12 +116,12 @@ func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command,
actionConfig := &action.Configuration{
Releases: store,
KubeClient: &environment.PrintingKubeClient{Out: ioutil.Discard},
KubeClient: &kube.PrintingKubeClient{Out: ioutil.Discard},
Discovery: fake.NewSimpleClientset().Discovery(),
Log: func(format string, v ...interface{}) {},
}
root := newRootCmd(nil, actionConfig, buf, args)
root := newRootCmd(actionConfig, buf, args)
root.SetOutput(buf)
root.SetArgs(args)
@ -136,35 +137,11 @@ type cmdTestCase struct {
golden string
wantError bool
// Rels are the available releases at the start of the test.
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
rels []*release.Release
}
// deprecated: Switch to executeActionCommandC
func executeCommandC(client helm.Interface, cmd string) (*cobra.Command, string, error) {
args, err := shellwords.Parse(cmd)
if err != nil {
return nil, "", err
}
buf := new(bytes.Buffer)
actionConfig := &action.Configuration{
Releases: storage.Init(driver.NewMemory()),
}
root := newRootCmd(client, actionConfig, buf, args)
root.SetOutput(buf)
root.SetArgs(args)
c, err := root.ExecuteC()
return c, buf.String(), err
func executeActionCommand(cmd string) (*cobra.Command, string, error) {
return executeActionCommandC(storageFixture(), cmd)
}
// ensureTestHome creates a home directory like ensureHome, but without remote references.

@ -17,31 +17,15 @@ limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"github.com/ghodss/yaml"
"github.com/gosuri/uitable"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/action"
)
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 = `
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
`
type historyOptions struct {
colWidth uint // --col-width
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}
func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewHistory(cfg)
cmd := &cobra.Command{
Use: "history RELEASE_NAME",
@ -78,94 +52,18 @@ func newHistoryCmd(c helm.Interface, out io.Writer) *cobra.Command {
Aliases: []string{"hist"},
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
o.client = ensureHelmClient(o.client, false)
o.release = args[0]
return o.run(out)
history, err := client.Run(args[0])
if err != nil {
return err
}
fmt.Fprintln(out, history)
return nil
},
}
f := cmd.Flags()
f.IntVar(&o.max, "max", 256, "maximum number of revision to include in history")
f.UintVar(&o.colWidth, "col-width", 60, "specifies the max column width of output")
f.StringVarP(&o.outputFormat, "output", "o", "table", "prints the output in the specified format (json|table|yaml)")
f.StringVarP(&client.OutputFormat, "output", "o", action.Table.String(), "prints the output in the specified format (json|table|yaml)")
f.IntVar(&client.Max, "max", 256, "maximum number of revision to include in history")
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 (
"testing"
rpb "k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/release"
)
func TestHistoryCmd(t *testing.T) {
mk := func(name string, vers int, status rpb.Status) *rpb.Release {
return helm.ReleaseMock(&helm.MockReleaseOptions{
mk := func(name string, vers int, status release.Status) *release.Release {
return release.Mock(&release.MockReleaseOptions{
Name: name,
Version: vers,
Status: status,
@ -35,35 +34,35 @@ func TestHistoryCmd(t *testing.T) {
tests := []cmdTestCase{{
name: "get history for release",
cmd: "history angry-bird",
rels: []*rpb.Release{
mk("angry-bird", 4, rpb.StatusDeployed),
mk("angry-bird", 3, rpb.StatusSuperseded),
mk("angry-bird", 2, rpb.StatusSuperseded),
mk("angry-bird", 1, rpb.StatusSuperseded),
rels: []*release.Release{
mk("angry-bird", 4, release.StatusDeployed),
mk("angry-bird", 3, release.StatusSuperseded),
mk("angry-bird", 2, release.StatusSuperseded),
mk("angry-bird", 1, release.StatusSuperseded),
},
golden: "output/history.txt",
}, {
name: "get history with max limit set",
cmd: "history angry-bird --max 2",
rels: []*rpb.Release{
mk("angry-bird", 4, rpb.StatusDeployed),
mk("angry-bird", 3, rpb.StatusSuperseded),
rels: []*release.Release{
mk("angry-bird", 4, release.StatusDeployed),
mk("angry-bird", 3, release.StatusSuperseded),
},
golden: "output/history-limit.txt",
}, {
name: "get history with yaml output format",
cmd: "history angry-bird --output yaml",
rels: []*rpb.Release{
mk("angry-bird", 4, rpb.StatusDeployed),
mk("angry-bird", 3, rpb.StatusSuperseded),
rels: []*release.Release{
mk("angry-bird", 4, release.StatusDeployed),
mk("angry-bird", 3, release.StatusSuperseded),
},
golden: "output/history.yaml",
}, {
name: "get history with json output format",
cmd: "history angry-bird --output json",
rels: []*rpb.Release{
mk("angry-bird", 4, rpb.StatusDeployed),
mk("angry-bird", 3, rpb.StatusSuperseded),
rels: []*release.Release{
mk("angry-bird", 4, release.StatusDeployed),
mk("angry-bird", 3, release.StatusSuperseded),
},
golden: "output/history.json",
}}

@ -29,7 +29,7 @@ import (
"k8s.io/helm/cmd/helm/require"
"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/installer"
"k8s.io/helm/pkg/repo"

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

@ -17,29 +17,19 @@ limitations under the License.
package main
import (
"bytes"
"fmt"
"io"
"path/filepath"
"regexp"
"strings"
"text/tabwriter"
"text/template"
"time"
"github.com/Masterminds/sprig"
"github.com/pkg/errors"
"k8s.io/helm/pkg/release"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/action"
"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/getter"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
)
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'.
`
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 {
o := &installOptions{cfg: cfg}
client := action.NewInstall(cfg)
cmd := &cobra.Command{
Use: "install [NAME] [CHART]",
Short: "install a chart",
Long: installDesc,
Args: require.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
debug("Original chart version: %q", o.version)
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)
RunE: func(_ *cobra.Command, args []string) error {
rel, err := runInstall(args, client, out)
if err != nil {
return err
}
o.name = name // FIXME
cp, err := o.locateChart(chart)
if err != nil {
return err
}
o.chartPath = cp
return o.run(out)
action.PrintRelease(out, rel)
return nil
},
}
f := cmd.Flags()
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)
addInstallFlags(cmd.Flags(), client)
return cmd
}
// nameAndChart returns the name of the release and the chart that should be used.
//
// This will read the flags and handle name generation if necessary.
func (o *installOptions) nameAndChart(args []string) (string, string, error) {
flagsNotSet := func() error {
if o.generateName {
return errors.New("cannot set --generate-name and also specify a name")
}
if o.nameTemplate != "" {
return errors.New("cannot set --name-template and also specify a name")
}
return nil
}
if len(args) == 2 {
return args[0], args[1], flagsNotSet()
}
func addInstallFlags(f *pflag.FlagSet, client *action.Install) {
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
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")
f.BoolVarP(&client.GenerateName, "generate-name", "g", false, "generate the name (and omit the NAME parameter)")
f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release")
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart")
addValueOptionsFlags(f, &client.ValueOptions)
addChartPathOptionsFlags(f, &client.ChartPathOptions)
}
if o.nameTemplate != "" {
newName, err := templateName(o.nameTemplate)
return newName, args[0], err
}
func addValueOptionsFlags(f *pflag.FlagSet, v *action.ValueOptions) {
f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL(can specify multiple)")
f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
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 {
return "", args[0], errors.New("must either provide a name or specify --generate-name")
}
func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.StringVar(&c.Version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed")
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])
if base == "." || base == "" {
base = "chart"
func runInstall(args []string, client *action.Install, out io.Writer) (*release.Release, error) {
debug("Original chart version: %q", client.Version)
if client.Version == "" && client.Devel {
debug("setting version to >0.0.0-0")
client.Version = ">0.0.0-0"
}
newName := fmt.Sprintf("%s-%d", base, time.Now().Unix())
return newName, args[0], nil
}
func (o *installOptions) run(out io.Writer) error {
debug("CHART PATH: %s\n", o.chartPath)
name, chart, err := client.NameAndChart(args)
if err != nil {
return nil, err
}
client.ReleaseName = name
rawVals, err := o.mergedValues()
cp, err := client.ChartPathOptions.LocateChart(chart, settings)
if err != nil {
return err
return nil, err
}
// If template is specified, try to run the template.
if o.nameTemplate != "" {
o.name, err = templateName(o.nameTemplate)
if err != nil {
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)
debug("CHART PATH: %s\n", cp)
if err := client.ValueOptions.MergeValues(settings); err != nil {
return nil, err
}
// Check chart dependencies to make sure all are present in /charts
chartRequested, err := loader.Load(o.chartPath)
chartRequested, err := loader.Load(cp)
if err != nil {
return err
return nil, err
}
validInstallableChart, err := chartutil.IsChartInstallable(chartRequested)
if !validInstallableChart {
return err
return nil, err
}
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:
// https://github.com/helm/helm/issues/2209
if err := checkDependencies(chartRequested, req); err != nil {
if o.depUp {
if err := action.CheckDependencies(chartRequested, req); err != nil {
if client.DependencyUpdate {
man := &downloader.Manager{
Out: out,
ChartPath: o.chartPath,
ChartPath: cp,
HelmHome: settings.Home,
Keyring: o.keyring,
Keyring: client.ChartPathOptions.Keyring,
SkipUpdate: false,
Getters: getter.All(settings),
}
if err := man.Update(); err != nil {
return err
return nil, err
}
} else {
return 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
return nil, err
}
}
missing = append(missing, r.Name)
}
if len(missing) > 0 {
return errors.Errorf("found in Chart.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", "))
}
return nil
client.Namespace = getNamespace()
return client.Run(chartRequested)
}

@ -17,8 +17,6 @@ limitations under the License.
package main
import (
"reflect"
"regexp"
"testing"
)
@ -142,133 +140,3 @@ func TestInstall(t *testing.T) {
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 (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/lint"
"k8s.io/helm/pkg/lint/support"
"k8s.io/helm/pkg/strvals"
"k8s.io/helm/pkg/action"
)
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.
`
type lintOptions struct {
strict bool
paths []string
valuesOptions
}
func newLintCmd(out io.Writer) *cobra.Command {
o := &lintOptions{paths: []string{"."}}
client := action.NewLint()
cmd := &cobra.Command{
Use: "lint PATH",
Short: "examines a chart for possible issues",
Long: longLintHelp,
RunE: func(cmd *cobra.Command, args []string) error {
paths := []string{"."}
if len(args) > 0 {
o.paths = args
paths = args
}
return o.run(out)
},
}
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
client.Namespace = getNamespace()
if err := client.ValueOptions.MergeValues(settings); err != nil {
return err
}
} else {
fmt.Println("==> Linting", path)
if len(linter.Messages) == 0 {
fmt.Println("Lint OK")
result := client.Run(paths)
var message strings.Builder
fmt.Fprintf(&message, "%d chart(s) linted, %d chart(s) failed\n", result.TotalChartsLinted, len(result.Errors))
for _, err := range result.Errors {
fmt.Fprintf(&message, "\t%s\n", err)
}
for _, msg := range linter.Messages {
fmt.Println(msg)
for _, msg := range result.Messages {
fmt.Fprintf(&message, "\t%s\n", msg)
}
total = total + 1
if linter.HighestSeverity >= lowestTolerance {
failures = failures + 1
if len(result.Errors) > 0 {
return errors.New(message.String())
}
}
fmt.Println("")
}
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)
fmt.Fprintf(out, message.String())
return nil
},
}
// 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")
}
}
f := cmd.Flags()
f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings")
addValueOptionsFlags(f, &client.ValueOptions)
// User specified a value via --set-string
for _, value := range o.stringValues {
if err := strvals.ParseIntoString(value, base); err != nil {
return base, errors.Wrap(err, "failed parsing --set-string data")
}
}
return base, nil
return cmd
}

@ -19,14 +19,11 @@ package main
import (
"fmt"
"io"
"strings"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/hapi/release"
)
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
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.
Only items that match the filter will be returned.
$ helm list 'ara[a-z]+'
$ helm list --filter 'ara[a-z]+'
NAME UPDATED CHART
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.
`
type listOptions struct {
// flags
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{}
func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewList(cfg)
cmd := &cobra.Command{
Use: "list [FILTER]",
Use: "list",
Short: "list releases",
Long: listHelp,
Aliases: []string{"ls"},
Args: require.MaximumNArgs(1),
Args: require.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
o.filter = strings.Join(args, " ")
}
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
if client.AllNamespaces {
client.SetConfiguration(newActionConfig(true))
}
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 {
fmt.Fprintln(out, res.Name)
}
return err
}
fmt.Fprintln(out, formatList(results, 90))
fmt.Fprintln(out, action.FormatList(results))
return err
},
}
f := cmd.Flags()
f.BoolVarP(&o.short, "short", "q", false, "output short (quiet) listing format")
f.BoolVarP(&o.byDate, "date", "d", false, "sort by release date")
f.BoolVarP(&o.sortDesc, "reverse", "r", false, "reverse the sort order")
f.IntVarP(&o.limit, "max", "m", 256, "maximum number of releases to fetch")
f.IntVarP(&o.offset, "offset", "o", 0, "next release name in the list, used to offset from start value")
f.BoolVarP(&o.all, "all", "a", false, "show all releases, not just the ones marked deployed")
f.BoolVar(&o.uninstalled, "uninstalled", false, "show uninstalled releases")
f.BoolVar(&o.superseded, "superseded", false, "show superseded releases")
f.BoolVar(&o.uninstalling, "uninstalling", false, "show releases that are currently being uninstalled")
f.BoolVar(&o.deployed, "deployed", false, "show deployed releases. If no other is specified, this will be automatically enabled")
f.BoolVar(&o.failed, "failed", false, "show failed releases")
f.BoolVar(&o.pending, "pending", false, "show pending releases")
f.UintVar(&o.colWidth, "col-width", 60, "specifies the max column width of output")
f.BoolVar(&o.allNamespaces, "all-namespaces", false, "list releases across all namespaces")
f.BoolVarP(&client.Short, "short", "q", false, "output short (quiet) listing format")
f.BoolVarP(&client.ByDate, "date", "d", false, "sort by release date")
f.BoolVarP(&client.SortDesc, "reverse", "r", false, "reverse the sort order")
f.BoolVarP(&client.All, "all", "a", false, "show all releases, not just the ones marked deployed")
f.BoolVar(&client.Uninstalled, "uninstalled", false, "show uninstalled releases")
f.BoolVar(&client.Superseded, "superseded", false, "show superseded releases")
f.BoolVar(&client.Uninstalling, "uninstalling", false, "show releases that are currently being uninstalled")
f.BoolVar(&client.Deployed, "deployed", false, "show deployed releases. If no other is specified, this will be automatically enabled")
f.BoolVar(&client.Failed, "failed", false, "show failed releases")
f.BoolVar(&client.Pending, "pending", false, "show pending releases")
f.BoolVar(&client.AllNamespaces, "all-namespaces", false, "list releases across all namespaces")
f.IntVarP(&client.Limit, "max", "m", 256, "maximum number of releases to fetch")
f.IntVarP(&client.Offset, "offset", "o", 0, "next release name in the list, used to offset from start value")
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
}
// 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"
"io"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"github.com/Masterminds/semver"
"k8s.io/helm/pkg/action"
"github.com/pkg/errors"
"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/getter"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/provenance"
)
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.
`
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 {
o := &packageOptions{}
client := action.NewPackage()
cmd := &cobra.Command{
Use: "package [CHART_PATH] [...]",
Short: "package a chart directory into a chart archive",
Long: packageDesc,
RunE: func(cmd *cobra.Command, args []string) error {
o.home = settings.Home
if len(args) == 0 {
return errors.Errorf("need at least one argument, the path to the chart")
}
if o.sign {
if o.key == "" {
if client.Sign {
if client.Key == "" {
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")
}
}
if err := client.ValueOptions.MergeValues(settings); err != nil {
return err
}
for i := 0; i < len(args); i++ {
o.path = args[i]
if err := o.run(out); err != nil {
path, err := filepath.Abs(args[i])
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
}
fmt.Fprintf(out, "Successfully packaged chart and saved it to: %s\n", p)
}
return nil
},
}
f := cmd.Flags()
f.BoolVar(&o.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(&o.keyring, "keyring", defaultKeyring(), "location of a public keyring")
f.StringVar(&o.version, "version", "", "set the version on the chart to this semver version")
f.StringVar(&o.appVersion, "app-version", "", "set the appVersion on the chart to this version")
f.StringVarP(&o.destination, "destination", "d", ".", "location to write the chart.")
f.BoolVarP(&o.dependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`)
o.valuesOptions.addFlags(f)
f.BoolVar(&client.Sign, "sign", false, "use a PGP private key to sign this package")
f.StringVar(&client.Key, "key", "", "name of the key to use when signing. Used if --sign is true")
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "location of a public keyring")
f.StringVar(&client.Version, "version", "", "set the version on the chart to this semver version")
f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version")
f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.")
f.BoolVarP(&client.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`)
addValueOptionsFlags(f, &client.ValueOptions)
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/loader"
"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) {
statExe := "stat"
statFileMsg := "no such file or directory"

@ -22,7 +22,7 @@ import (
"github.com/spf13/cobra"
"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/installer"
)

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

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

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

@ -24,7 +24,7 @@ import (
"github.com/pkg/errors"
"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/installer"
)

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

@ -19,18 +19,11 @@ package main
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/downloader"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/repo"
"k8s.io/helm/pkg/action"
)
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.
`
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 {
o := &pullOptions{}
client := action.NewPull()
cmd := &cobra.Command{
Use: "pull [chart URL | repo/chartname] [...]",
@ -70,95 +51,30 @@ func newPullCmd(out io.Writer) *cobra.Command {
Long: pullDesc,
Args: require.MinimumNArgs(1),
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")
o.version = ">0.0.0-0"
client.Version = ">0.0.0-0"
}
for i := 0; i < len(args); i++ {
o.chartRef = args[i]
if err := o.run(out); err != nil {
output, err := client.Run(args[i])
if err != nil {
return err
}
fmt.Fprint(out, output)
}
return nil
},
}
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(&o.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.StringVar(&o.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")
o.chartPathOptions.addFlags(f)
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.BoolVar(&client.Untar, "untar", false, "if set to true, will untar the chart after downloading it")
f.BoolVar(&client.VerifyLater, "prov", false, "fetch the provenance file, but don't perform verification")
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(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this")
addChartPathOptionsFlags(f, &client.ChartPathOptions)
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)
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 tt.wantError {
continue

@ -24,8 +24,8 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/release"
)
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.
`
type releaseTestOptions struct {
name string
client helm.Interface
timeout int64
cleanup bool
}
func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
o := &releaseTestOptions{client: c}
func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewReleaseTesting(cfg)
cmd := &cobra.Command{
Use: "test [RELEASE]",
@ -51,47 +44,37 @@ func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
Long: releaseTestDesc,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
o.name = args[0]
o.client = ensureHelmClient(o.client, false)
return o.run(out)
c, errc := client.Run(args[0])
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")
}
}
},
}
f := cmd.Flags()
f.Int64Var(&o.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&o.cleanup, "cleanup", false, "delete test pods upon completion")
f.Int64Var(&client.Timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&client.Cleanup, "cleanup", false, "delete test pods upon completion")
return cmd
}
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 {
failed int
}

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

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

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

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

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

@ -24,7 +24,7 @@ import (
"testing"
"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/repotest"
)

@ -19,13 +19,11 @@ package main
import (
"fmt"
"io"
"strconv"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/action"
)
const rollbackDesc = `
@ -36,20 +34,8 @@ second is a revision (version) number. To see revision numbers, run
'helm history RELEASE'.
`
type rollbackOptions struct {
name string
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}
func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewRollback(cfg)
cmd := &cobra.Command{
Use: "rollback [RELEASE] [REVISION]",
@ -57,45 +43,25 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command {
Long: rollbackDesc,
Args: require.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
o.name = args[0]
v64, err := strconv.ParseInt(args[1], 10, 32)
_, err := client.Run(args[0])
if err != nil {
return errors.Wrapf(err, "invalid revision number '%q'", args[1])
return err
}
o.revision = int(v64)
o.client = ensureHelmClient(o.client, false)
return o.run(out)
fmt.Fprintf(out, "Rollback was a success! Happy Helming!\n")
return nil
},
}
f := cmd.Flags()
f.BoolVar(&o.dryRun, "dry-run", false, "simulate a rollback")
f.BoolVar(&o.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&o.force, "force", false, "force resource update through delete/recreate if needed")
f.BoolVar(&o.disableHooks, "no-hooks", false, "prevent hooks from running during rollback")
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.IntVarP(&client.Version, "version", "v", 0, "revision number to rollback to (default: rollback to previous release)")
f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback")
f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&client.Force, "force", false, "force resource update through delete/recreate if needed")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during rollback")
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
}
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 (
"testing"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/release"
)
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{{
name: "rollback a release",
cmd: "rollback funny-honey 1",
golden: "output/rollback.txt",
rels: rels,
}, {
name: "rollback a release with timeout",
cmd: "rollback funny-honey 1 --timeout 120",
golden: "output/rollback-timeout.txt",
rels: rels,
}, {
name: "rollback a release with wait",
cmd: "rollback funny-honey 1 --wait",
golden: "output/rollback-wait.txt",
rels: rels,
}, {
name: "rollback a release without revision",
cmd: "rollback funny-honey",
golden: "output/rollback-no-args.txt",
rels: rels,
wantError: true,
}}
runTestCmd(t, tests)

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

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

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

@ -19,14 +19,11 @@ package main
import (
"fmt"
"io"
"strings"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/action"
)
const showDesc = `
@ -51,24 +48,8 @@ This command inspects a chart (directory, file, or URL) and displays the content
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 {
o := &showOptions{output: all}
client := action.NewShow(action.ShowAll)
showCommand := &cobra.Command{
Use: "show [CHART]",
@ -77,12 +58,16 @@ func newShowCmd(out io.Writer) *cobra.Command {
Long: showDesc,
Args: require.ExactArgs(1),
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 {
return err
}
o.chartpath = cp
return o.run(out)
output, err := client.Run(cp)
if err != nil {
return err
}
fmt.Fprint(out, output)
return nil
},
}
@ -92,13 +77,17 @@ func newShowCmd(out io.Writer) *cobra.Command {
Long: showValuesDesc,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
o.output = valuesOnly
cp, err := o.locateChart(args[0])
client.OutputFormat = action.ShowValues
cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil {
return err
}
o.chartpath = cp
return o.run(out)
output, err := client.Run(cp)
if err != nil {
return err
}
fmt.Fprint(out, output)
return nil
},
}
@ -108,13 +97,17 @@ func newShowCmd(out io.Writer) *cobra.Command {
Long: showChartDesc,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
o.output = chartOnly
cp, err := o.locateChart(args[0])
client.OutputFormat = action.ShowChart
cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil {
return err
}
o.chartpath = cp
return o.run(out)
output, err := client.Run(cp)
if err != nil {
return err
}
fmt.Fprint(out, output)
return nil
},
}
@ -124,19 +117,23 @@ func newShowCmd(out io.Writer) *cobra.Command {
Long: readmeChartDesc,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
o.output = readmeOnly
cp, err := o.locateChart(args[0])
client.OutputFormat = action.ShowReadme
cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil {
return err
}
o.chartpath = cp
return o.run(out)
output, err := client.Run(cp)
if err != nil {
return err
}
fmt.Fprint(out, output)
return nil
},
}
cmds := []*cobra.Command{showCommand, readmeSubCmd, valuesSubCmd, chartSubCmd}
for _, subCmd := range cmds {
o.chartPathOptions.addFlags(subCmd.Flags())
addChartPathOptionsFlags(subCmd.Flags(), &client.ChartPathOptions)
}
for _, subCmd := range cmds[1:] {
@ -145,52 +142,3 @@ func newShowCmd(out io.Writer) *cobra.Command {
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 (
"encoding/json"
"fmt"
"io"
"regexp"
"text/tabwriter"
"github.com/ghodss/yaml"
"github.com/gosuri/uitable"
"github.com/gosuri/uitable/util/strutil"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/action"
)
var statusHelp = `
@ -45,15 +39,8 @@ The status consists of:
- additional notes provided by the chart
`
type statusOptions struct {
release string
client helm.Interface
version int
outfmt string
}
func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command {
o := &statusOptions{client: client}
func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewStatus(cfg)
cmd := &cobra.Command{
Use: "status RELEASE_NAME",
@ -61,88 +48,44 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command {
Long: statusHelp,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
o.release = args[0]
o.client = ensureHelmClient(o.client, false)
return o.run(out)
rel, err := client.Run(args[0])
if err != nil {
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")
cmd.PersistentFlags().StringVarP(&o.outfmt, "output", "o", "", "output the status in the specified format (json or yaml)")
f := cmd.PersistentFlags()
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
}
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"
"time"
"k8s.io/helm/pkg/hapi/release"
"k8s.io/helm/pkg/chart"
"k8s.io/helm/pkg/release"
)
func TestStatusCmd(t *testing.T) {
releasesMockWithStatus := func(info *release.Info) []*release.Release {
info.LastDeployed = time.Unix(1452902400, 0).UTC()
return []*release.Release{{
Name: "flummoxed-chickadee",
Info: info,
Name: "flummoxed-chickadee",
Info: info,
Chart: &chart.Chart{},
}}
}

@ -19,39 +19,23 @@ package main
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"regexp"
"io/ioutil"
"strings"
"time"
"github.com/Masterminds/semver"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/helm/cmd/helm/require"
"k8s.io/helm/pkg/chart/loader"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/engine"
"k8s.io/helm/pkg/hapi/release"
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)
"k8s.io/helm/pkg/action"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"
)
const templateDesc = `
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
of the server-side testing of chart validity (e.g. whether an API is supported)
is done.
@ -61,232 +45,38 @@ To render just one template in a chart, use '-x':
$ 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 {
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{
Use: "template CHART",
Short: fmt.Sprintf("locally render templates"),
Long: templateDesc,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// verify chart path exists
if _, err := os.Stat(args[0]); err == nil {
if o.chartPath, err = filepath.Abs(args[0]); err != nil {
return err
}
} else {
RunE: func(_ *cobra.Command, args []string) error {
client.DryRun = true
client.ReleaseName = "RELEASE-NAME"
client.Replace = true // Skip the name check
rel, err := runInstall(args, client, out)
if err != nil {
return err
}
return o.run(out)
fmt.Fprintln(out, strings.TrimSpace(rel.Manifest))
return nil
},
}
f := cmd.Flags()
f.BoolVar(&o.showNotes, "notes", false, "show the computed NOTES.txt file as well")
f.StringVarP(&o.releaseName, "name", "", "RELEASE-NAME", "release name")
f.StringArrayVarP(&o.renderFiles, "execute", "x", []string{}, "only execute the given templates")
f.StringVar(&o.nameTemplate, "name-template", "", "specify template used to name the release")
f.StringVar(&o.kubeVersion, "kube-version", defaultKubeVersion, "kubernetes version used as Capabilities.KubeVersion.Major/Minor")
f.StringVar(&o.outputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
o.valuesOptions.addFlags(f)
addInstallFlags(cmd.Flags(), client)
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"
func TestTemplateCmd(t *testing.T) {
absChartPath, err := filepath.Abs(chartPath)
if err != nil {
t.Fatal(err)
}
tests := []cmdTestCase{
{
name: "check name",
@ -37,24 +33,9 @@ func TestTemplateCmd(t *testing.T) {
},
{
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",
},
{
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",
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),
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",
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
NAMESPACE: default
STATUS: deployed

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

@ -1,4 +1,4 @@
NAME: aeneas
NAME: aeneas
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed

@ -1,4 +1,4 @@
NAME: virgil
NAME: virgil
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed

@ -1,4 +1,4 @@
NAME: virgil
NAME: virgil
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed

@ -1,4 +1,4 @@
NAME: foobar
NAME: foobar
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed

@ -1,4 +1,4 @@
NAME: virgil
NAME: virgil
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed

@ -1,4 +1,4 @@
NAME: virgil
NAME: virgil
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed

@ -1,4 +1,4 @@
NAME: apollo
NAME: apollo
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
STATUS: deployed

@ -1,4 +1,4 @@
NAME: aeneas
NAME: aeneas
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
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
NAMESPACE:
STATUS: deployed

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

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

@ -1,3 +1,4 @@
NAME: flummoxed-chickadee
LAST DEPLOYED: 2016-01-16 00:00:00 +0000 UTC
NAMESPACE:
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
selector:
app: subcharta
---
# Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1
@ -33,7 +32,6 @@ spec:
name: nginx
selector:
app: subchartb
---
# Source: subchart1/templates/service.yaml
apiVersion: v1
@ -55,4 +53,3 @@ spec:
name: nginx
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: "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
apiVersion: v1
kind: Service
@ -19,4 +53,3 @@ spec:
name: apache
selector:
app: subchart1

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

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

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

@ -1,2 +1,11 @@
FAILURE: red lights everywhere
Error: 1 test(s) failed
NAME: test-failure
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!
NAME: crazy-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
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!
NAME: zany-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
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!
NAME: funny-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
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!
NAME: funny-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
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!
NAME: funny-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
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!
NAME: crazy-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
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!
NAME: funny-bunny
LAST DEPLOYED: 1977-09-02 22:04:05 +0000 UTC
NAMESPACE: default
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