Commit 36e6094c authored by Matt Butcher's avatar Matt Butcher

feat(helm): add 'helm upgrade --install' support

This makes it possible to run an upgrade that will install a release if
it doesn't already exist.

Closes #1042
parent 2d449d3e
...@@ -21,10 +21,12 @@ import ( ...@@ -21,10 +21,12 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/storage/driver"
) )
const upgradeDesc = ` const upgradeDesc = `
...@@ -48,6 +50,8 @@ type upgradeCmd struct { ...@@ -48,6 +50,8 @@ type upgradeCmd struct {
values *values values *values
verify bool verify bool
keyring string keyring string
install bool
namespace string
} }
func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
...@@ -83,41 +87,45 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { ...@@ -83,41 +87,45 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks") f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks")
f.BoolVar(&upgrade.verify, "verify", false, "verify the provenance of the chart before upgrading") f.BoolVar(&upgrade.verify, "verify", false, "verify the provenance of the chart before upgrading")
f.StringVar(&upgrade.keyring, "keyring", defaultKeyring(), "the path to the keyring that contains public singing keys") f.StringVar(&upgrade.keyring, "keyring", defaultKeyring(), "the path to the keyring that contains public singing keys")
f.BoolVarP(&upgrade.install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
f.StringVar(&upgrade.namespace, "namespace", "default", "the namespace to install the release into (only used if --install is set)")
return cmd return cmd
} }
func (u *upgradeCmd) vals() ([]byte, error) {
var buffer bytes.Buffer
// User specified a values file via -f/--values
if u.valuesFile != "" {
bytes, err := ioutil.ReadFile(u.valuesFile)
if err != nil {
return []byte{}, err
}
buffer.Write(bytes)
}
// User specified value pairs via --set
// These override any values in the specified file
if len(u.values.pairs) > 0 {
bytes, err := u.values.yaml()
if err != nil {
return []byte{}, err
}
buffer.Write(bytes)
}
return buffer.Bytes(), nil
}
func (u *upgradeCmd) run() error { func (u *upgradeCmd) run() error {
chartPath, err := locateChartPath(u.chart, u.verify, u.keyring) chartPath, err := locateChartPath(u.chart, u.verify, u.keyring)
if err != nil { if err != nil {
return err return err
} }
if u.install {
// If a release does not exist, install it. If another error occurs during
// the check, ignore the error and continue with the upgrade.
//
// The returned error is a grpc.rpcError that wraps the message from the original error.
// So we're stuck doing string matching against the wrapped error, which is nested somewhere
// inside of the grpc.rpcError message.
_, err := u.client.ReleaseContent(u.release, helm.ContentReleaseVersion(1))
if err != nil && strings.Contains(err.Error(), driver.ErrReleaseNotFound.Error()) {
fmt.Fprintf(u.out, "Release %q does not exist. Installing it now.\n", u.release)
ic := &installCmd{
chartPath: chartPath,
client: u.client,
out: u.out,
name: u.release,
valuesFile: u.valuesFile,
dryRun: u.dryRun,
verify: u.verify,
disableHooks: u.disableHooks,
keyring: u.keyring,
values: u.values,
namespace: u.namespace,
}
return ic.run()
}
}
rawVals, err := u.vals() rawVals, err := u.vals()
if err != nil { if err != nil {
return err return err
...@@ -139,5 +147,29 @@ func (u *upgradeCmd) run() error { ...@@ -139,5 +147,29 @@ func (u *upgradeCmd) run() error {
PrintStatus(u.out, status) PrintStatus(u.out, status)
return nil return nil
}
func (u *upgradeCmd) vals() ([]byte, error) {
var buffer bytes.Buffer
// User specified a values file via -f/--values
if u.valuesFile != "" {
bytes, err := ioutil.ReadFile(u.valuesFile)
if err != nil {
return []byte{}, err
}
buffer.Write(bytes)
}
// User specified value pairs via --set
// These override any values in the specified file
if len(u.values.pairs) > 0 {
bytes, err := u.values.yaml()
if err != nil {
return []byte{}, err
}
buffer.Write(bytes)
}
return buffer.Bytes(), nil
} }
...@@ -69,6 +69,13 @@ func TestUpgradeCmd(t *testing.T) { ...@@ -69,6 +69,13 @@ func TestUpgradeCmd(t *testing.T) {
resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 2, chart: ch}), resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 2, chart: ch}),
expected: "funny-bunny has been upgraded. Happy Helming!\n", expected: "funny-bunny has been upgraded. Happy Helming!\n",
}, },
{
name: "install a release with 'upgrade --install'",
args: []string{"zany-bunny", chartPath},
flags: []string{"-i"},
resp: releaseMock(&releaseOptions{name: "zany-bunny", version: 1, chart: ch}),
expected: "zany-bunny has been upgraded. Happy Helming!\n",
},
} }
cmd := func(c *fakeReleaseClient, out io.Writer) *cobra.Command { cmd := func(c *fakeReleaseClient, out io.Writer) *cobra.Command {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment