diff --git a/cmd/helm/delete_test.go b/cmd/helm/delete_test.go index b67afe579..517cbd22e 100644 --- a/cmd/helm/delete_test.go +++ b/cmd/helm/delete_test.go @@ -31,14 +31,14 @@ func TestDelete(t *testing.T) { args: []string{"aeneas"}, flags: []string{}, expected: "", // Output of a delete is an empty string and exit 0. - resp: releaseMock("aeneas"), + resp: releaseMock(&releaseOptions{name: "aeneas"}), }, { name: "delete without hooks", args: []string{"aeneas"}, flags: []string{"--no-hooks"}, expected: "", - resp: releaseMock("aeneas"), + resp: releaseMock(&releaseOptions{name: "aeneas"}), }, { name: "delete without release", diff --git a/cmd/helm/get_hooks_test.go b/cmd/helm/get_hooks_test.go index 212da53bc..8d9eda2d5 100644 --- a/cmd/helm/get_hooks_test.go +++ b/cmd/helm/get_hooks_test.go @@ -29,7 +29,7 @@ func TestGetHooks(t *testing.T) { name: "get hooks with release", args: []string{"aeneas"}, expected: mockHookTemplate, - resp: releaseMock("aeneas"), + resp: releaseMock(&releaseOptions{name: "aeneas"}), }, { name: "get hooks without args", diff --git a/cmd/helm/get_manifest_test.go b/cmd/helm/get_manifest_test.go index f09ecd3c2..47d6d9053 100644 --- a/cmd/helm/get_manifest_test.go +++ b/cmd/helm/get_manifest_test.go @@ -29,7 +29,7 @@ func TestGetManifest(t *testing.T) { name: "get manifest with release", args: []string{"juno"}, expected: mockManifest, - resp: releaseMock("juno"), + resp: releaseMock(&releaseOptions{name: "juno"}), }, { name: "get manifest without args", diff --git a/cmd/helm/get_test.go b/cmd/helm/get_test.go index 962b23c04..95ecffeb0 100644 --- a/cmd/helm/get_test.go +++ b/cmd/helm/get_test.go @@ -27,7 +27,7 @@ func TestGetCmd(t *testing.T) { tests := []releaseCase{ { name: "get with a release", - resp: releaseMock("thomas-guide"), + resp: releaseMock(&releaseOptions{name: "thomas-guide"}), args: []string{"thomas-guide"}, expected: "VERSION: 1\nRELEASED: (.*)\nCHART: foo-0.1.0-beta.1\nUSER-SUPPLIED VALUES:\nname: \"value\"\nCOMPUTED VALUES:\nname: value\n\nHOOKS:\n---\n# pre-install-hook\n" + mockHookTemplate + "\nMANIFEST:", }, diff --git a/cmd/helm/get_values_test.go b/cmd/helm/get_values_test.go index 71aef1eb9..635161366 100644 --- a/cmd/helm/get_values_test.go +++ b/cmd/helm/get_values_test.go @@ -27,7 +27,7 @@ func TestGetValuesCmd(t *testing.T) { tests := []releaseCase{ { name: "get values with a release", - resp: releaseMock("thomas-guide"), + resp: releaseMock(&releaseOptions{name: "thomas-guide"}), args: []string{"thomas-guide"}, expected: "name: \"value\"", }, diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 93550bfcb..5af98960d 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -19,6 +19,7 @@ package main import ( "bytes" "io" + "math/rand" "regexp" "testing" @@ -44,16 +45,28 @@ metadata: name: fixture ` -func releaseMock(name string) *release.Release { +type releaseOptions struct { + name string + version int32 + chart *chart.Chart +} + +func releaseMock(opts *releaseOptions) *release.Release { date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} - return &release.Release{ - Name: name, - Info: &release.Info{ - FirstDeployed: &date, - LastDeployed: &date, - Status: &release.Status{Code: release.Status_DEPLOYED}, - }, - Chart: &chart.Chart{ + + name := opts.name + if name == "" { + name = "testrelease-" + string(rand.Intn(100)) + } + + var version int32 = 1 + if opts.version != 0 { + version = opts.version + } + + ch := opts.chart + if opts.chart == nil { + ch = &chart.Chart{ Metadata: &chart.Metadata{ Name: "foo", Version: "0.1.0-beta.1", @@ -61,9 +74,19 @@ func releaseMock(name string) *release.Release { Templates: []*chart.Template{ {Name: "foo.tpl", Data: []byte(mockManifest)}, }, + } + } + + return &release.Release{ + Name: name, + Info: &release.Info{ + FirstDeployed: &date, + LastDeployed: &date, + Status: &release.Status{Code: release.Status_DEPLOYED}, }, + Chart: ch, Config: &chart.Config{Raw: `name: "value"`}, - Version: 1, + Version: version, Hooks: []*release.Hook{ { Name: "pre-install-hook", @@ -108,7 +131,7 @@ func (c *fakeReleaseClient) ReleaseStatus(rlsName string, opts ...helm.StatusOpt return nil, nil } -func (c *fakeReleaseClient) UpdateRelease(rlsName string, opts ...helm.UpdateOption) (*rls.UpdateReleaseResponse, error) { +func (c *fakeReleaseClient) UpdateRelease(rlsName string, chStr string, opts ...helm.UpdateOption) (*rls.UpdateReleaseResponse, error) { return nil, nil } diff --git a/cmd/helm/install_test.go b/cmd/helm/install_test.go index c8f9e53bb..a9ddb9d5c 100644 --- a/cmd/helm/install_test.go +++ b/cmd/helm/install_test.go @@ -33,7 +33,7 @@ func TestInstall(t *testing.T) { args: []string{"testdata/testcharts/alpine"}, flags: strings.Split("--name aeneas", " "), expected: "aeneas", - resp: releaseMock("aeneas"), + resp: releaseMock(&releaseOptions{name: "aeneas"}), }, // Install, no hooks { @@ -41,14 +41,14 @@ func TestInstall(t *testing.T) { args: []string{"testdata/testcharts/alpine"}, flags: strings.Split("--name aeneas --no-hooks", " "), expected: "juno", - resp: releaseMock("juno"), + resp: releaseMock(&releaseOptions{name: "juno"}), }, // Install, values from cli { name: "install with values", args: []string{"testdata/testcharts/alpine"}, flags: strings.Split("--set foo=bar", " "), - resp: releaseMock("virgil"), + resp: releaseMock(&releaseOptions{name: "virgil"}), expected: "virgil", }, // Install, no charts diff --git a/cmd/helm/list_test.go b/cmd/helm/list_test.go index 6d44173cb..84fe65a47 100644 --- a/cmd/helm/list_test.go +++ b/cmd/helm/list_test.go @@ -36,7 +36,7 @@ func TestListCmd(t *testing.T) { { name: "with a release", resp: []*release.Release{ - releaseMock("thomas-guide"), + releaseMock(&releaseOptions{name: "thomas-guide"}), }, expected: "thomas-guide", }, @@ -44,7 +44,7 @@ func TestListCmd(t *testing.T) { name: "list --long", flags: map[string]string{"long": "1"}, resp: []*release.Release{ - releaseMock("atlas"), + releaseMock(&releaseOptions{name: "atlas"}), }, expected: "NAME \tVERSION\tUPDATED \tSTATUS \tCHART \natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\n", }, diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 639598abd..c4f75310f 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -19,6 +19,7 @@ package main import ( "fmt" "io" + "io/ioutil" "github.com/spf13/cobra" @@ -32,19 +33,13 @@ The upgrade arguments must be a release and a chart. The chart argument can be a relative path to a packaged or unpackaged chart. ` -// upgrade flags -var ( - // upgradeDryRun performs a dry-run upgrade - upgradeDryRun bool - // upgradeValues is the filename of supplied values. - upgradeValues string -) - type upgradeCmd struct { - release string - chart string - out io.Writer - client helm.Interface + release string + chart string + out io.Writer + client helm.Interface + dryRun bool + valuesFile string } func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { @@ -66,17 +61,15 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { upgrade.release = args[0] upgrade.chart = args[1] + upgrade.client = ensureHelmClient(upgrade.client) - if upgrade.client == nil { - upgrade.client = helm.NewClient(helm.HelmHost(helm.Config.ServAddr)) - } return upgrade.run() }, } f := cmd.Flags() - f.StringVarP(&upgradeValues, "values", "f", "", "path to a values YAML file") - f.BoolVar(&upgradeDryRun, "dry-run", false, "simulate an upgrade") + f.StringVarP(&upgrade.valuesFile, "values", "f", "", "path to a values YAML file") + f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade") return cmd } @@ -87,18 +80,20 @@ func (u *upgradeCmd) run() error { return err } - rawVals, err := vals(upgradeValues) - if err != nil { - return err + rawVals := []byte{} + if u.valuesFile != "" { + rawVals, err = ioutil.ReadFile(u.valuesFile) + if err != nil { + return err + } } - _, err = helm.UpdateRelease(u.release, chartPath, rawVals, upgradeDryRun) + _, err = u.client.UpdateRelease(u.release, chartPath, helm.UpdateValueOverrides(rawVals), helm.UpgradeDryRun(u.dryRun)) if err != nil { return prettyError(err) } - fmt.Println("\nIt's not you. It's me.") - fmt.Println("Your upgrade looks valid but this command is still in progress.\nHang tight.\n") + fmt.Fprintf(u.out, "It's not you. It's me\nYour upgrade looks valid but this command is still under active development.\nHang tight.\n") return nil diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go new file mode 100644 index 000000000..145ab28b1 --- /dev/null +++ b/cmd/helm/upgrade_test.go @@ -0,0 +1,73 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "io" + "io/ioutil" + "os" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +func TestUpgradeCmd(t *testing.T) { + tmpChart, _ := ioutil.TempDir("testdata", "tmp") + defer os.RemoveAll(tmpChart) + cfile := &chart.Metadata{ + Name: "testUpgradeChart", + Description: "A Helm chart for Kubernetes", + Version: "0.1.0", + } + chartPath, err := chartutil.Create(cfile, tmpChart) + if err != nil { + t.Errorf("Error creating chart for upgrade: %v", err) + } + ch, _ := chartutil.Load(chartPath) + _ = releaseMock(&releaseOptions{ + name: "funny-bunny", + chart: ch, + }) + + // update chart version + cfile = &chart.Metadata{ + Name: "testUpgradeChart", + Description: "A Helm chart for Kubernetes", + Version: "0.1.2", + } + chartPath, err = chartutil.Create(cfile, tmpChart) + ch, _ = chartutil.Load(chartPath) + + tests := []releaseCase{ + { + name: "upgrade a release", + args: []string{"funny-bunny", chartPath}, + resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 2, chart: ch}), + expected: "It's not you. It's me\nYour upgrade looks valid but this command is still under active development.\nHang tight.\n", + }, + } + + cmd := func(c *fakeReleaseClient, out io.Writer) *cobra.Command { + return newUpgradeCmd(c, out) + } + + runReleaseCases(t, tests, cmd) + +} diff --git a/pkg/helm/compat.go b/pkg/helm/compat.go deleted file mode 100644 index fcbea3d0f..000000000 --- a/pkg/helm/compat.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors All rights reserved. - -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 helm - -import ( - rls "k8s.io/helm/pkg/proto/hapi/services" -) - -// These APIs are a temporary abstraction layer that captures the interaction between the current cmd/helm and old -// pkg/helm implementations. Post refactor the cmd/helm package will use the APIs exposed on helm.Client directly. - -// Config is the base configuration -var Config struct { - ServAddr string -} - -// ListReleases lists releases. DEPRECATED. -// -// Soon to be deprecated helm ListReleases API. -func ListReleases(limit int, offset string, sort rls.ListSort_SortBy, order rls.ListSort_SortOrder, filter string) (*rls.ListReleasesResponse, error) { - opts := []ReleaseListOption{ - ReleaseListLimit(limit), - ReleaseListOffset(offset), - ReleaseListFilter(filter), - ReleaseListSort(int32(sort)), - ReleaseListOrder(int32(order)), - } - return NewClient(Host(Config.ServAddr)).ListReleases(opts...) -} - -// GetReleaseStatus gets a release status. DEPRECATED -// -// Soon to be deprecated helm GetReleaseStatus API. -func GetReleaseStatus(rlsName string) (*rls.GetReleaseStatusResponse, error) { - return NewClient(Host(Config.ServAddr)).ReleaseStatus(rlsName) -} - -// GetReleaseContent gets the content of a release. -// Soon to be deprecated helm GetReleaseContent API. -func GetReleaseContent(rlsName string) (*rls.GetReleaseContentResponse, error) { - return NewClient(Host(Config.ServAddr)).ReleaseContent(rlsName) -} - -// UpdateRelease updates a release. -// Soon to be deprecated helm UpdateRelease API. -func UpdateRelease(rlsName, chStr string, vals []byte, dryRun bool) (*rls.UpdateReleaseResponse, error) { - return NewClient(Host(Config.ServAddr)).UpdateRelease(rlsName, chStr) -} - -// InstallRelease runs an install for a release. -// Soon to be deprecated helm InstallRelease API. -func InstallRelease(vals []byte, rlsName, chStr string, dryRun bool) (*rls.InstallReleaseResponse, error) { - client := NewClient(Host(Config.ServAddr)) - if dryRun { - client.Option(DryRun()) - } - return client.InstallRelease(chStr, ValueOverrides(vals), ReleaseName(rlsName)) -} - -// UninstallRelease destroys an existing release. -// Soon to be deprecated helm UninstallRelease API. -func UninstallRelease(rlsName string, dryRun bool) (*rls.UninstallReleaseResponse, error) { - client := NewClient(Host(Config.ServAddr)) - if dryRun { - client.Option(DryRun()) - } - return client.DeleteRelease(rlsName) -} diff --git a/pkg/helm/option.go b/pkg/helm/option.go index 5fc5df066..e1d332c46 100644 --- a/pkg/helm/option.go +++ b/pkg/helm/option.go @@ -43,6 +43,8 @@ type options struct { listReq rls.ListReleasesRequest // release install options are applied directly to the install release request instReq rls.InstallReleaseRequest + // release update options are applied directly to the update release request + updateReq rls.UpdateReleaseRequest } // Home specifies the location of helm home, (default = "$HOME/.helm"). @@ -111,6 +113,13 @@ func ValueOverrides(raw []byte) InstallOption { } } +// UpdateValueOverrides specifies a list of values to include when upgrading +func UpdateValueOverrides(raw []byte) UpdateOption { + return func(opts *options) { + opts.updateReq.Values = &cpb.Config{Raw: string(raw)} + } +} + // ReleaseName specifies the name of the release when installing. func ReleaseName(name string) InstallOption { return func(opts *options) { @@ -132,6 +141,13 @@ func DeleteDryRun(dry bool) DeleteOption { } } +// UpgradeDryRun will (if true) execute an upgrade as a dry run. +func UpgradeDryRun(dry bool) UpdateOption { + return func(opts *options) { + opts.dryRun = dry + } +} + // InstallDisableHooks disables hooks during installation. func InstallDisableHooks(disable bool) InstallOption { return func(opts *options) { @@ -155,7 +171,9 @@ type StatusOption func(*options) // DeleteOption -- TODO type DeleteOption func(*options) -// UpdateOption -- TODO +// UpdateOption allows specifying various settings +// configurable by the helm client user for overriding +// the defaults used when running the `helm upgrade` command. type UpdateOption func(*options) // RPC helpers defined on `options` type. Note: These actually execute the