From 35bf1c66b4bfd87528b739c0f4e5c4d4c7f4c4b9 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Wed, 6 Jul 2016 16:38:49 -0600 Subject: [PATCH 1/7] feat(helm): add helm upgrade command --- cmd/helm/upgrade.go | 46 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 cmd/helm/upgrade.go diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go new file mode 100644 index 000000000..5b14d9f76 --- /dev/null +++ b/cmd/helm/upgrade.go @@ -0,0 +1,46 @@ +/* +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 ( + "fmt" + + "github.com/spf13/cobra" +) + +const upgradeDesc = ` +This command upgrades a release to a new version of a chart. + +The upgrade arguments must be a release and a chart. The chart +argument can be a relative path to a packaged or unpackaged chart. +` + +var upgradeCmd = &cobra.Command{ + Use: "upgrade [RELEASE] [CHART]", + Short: "upgrade a release", + Long: upgradeDesc, + RunE: runUpgrade, +} + +func init() { + RootCommand.AddCommand(upgradeCmd) +} + +func runUpgrade(cmd *cobra.Command, args []string) error { + fmt.Println("Coming Soon") + return nil +} From e633b4b75a796bfeb8cc996296aaacac880916fb Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Fri, 8 Jul 2016 16:30:28 -0600 Subject: [PATCH 2/7] feat(pkg/helm): fill in proto for UpdateRelease --- _proto/hapi/services/tiller.proto | 9 +++++++++ pkg/helm/interface.go | 2 +- pkg/proto/hapi/services/tiller.pb.go | 30 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index c4d8f8ff9..d04fa1a04 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -154,10 +154,19 @@ message GetReleaseContentResponse { // UpdateReleaseRequest updates a release. message UpdateReleaseRequest { + // The name of the release + string name = 1; + // Chart is the protobuf representation of a chart. + hapi.chart.Chart chart = 2; + // Values is a string containing (unparsed) YAML values. + hapi.chart.Config values = 3; + // dry_run, if true, will run through the release logic, but neither create + bool dry_run = 4; } // UpdateReleaseResponse is the response to an update request. message UpdateReleaseResponse { + hapi.release.Release release = 1; } // InstallReleaseRequest is the request for an installation of a chart. diff --git a/pkg/helm/interface.go b/pkg/helm/interface.go index bc2cc6e1e..528af8908 100644 --- a/pkg/helm/interface.go +++ b/pkg/helm/interface.go @@ -26,6 +26,6 @@ type Interface interface { InstallRelease(chStr, namespace string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) - UpdateRelease(rlsName string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) + UpdateRelease(rlsName, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error) } diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index 0f8f208d0..27f2e5046 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -218,6 +218,14 @@ func (m *GetReleaseContentResponse) GetRelease() *hapi_release3.Release { // UpdateReleaseRequest updates a release. type UpdateReleaseRequest struct { + // The name of the release + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Chart is the protobuf representation of a chart. + Chart *hapi_chart3.Chart `protobuf:"bytes,2,opt,name=chart" json:"chart,omitempty"` + // Values is a string containing (unparsed) YAML values. + Values *hapi_chart.Config `protobuf:"bytes,3,opt,name=values" json:"values,omitempty"` + // dry_run, if true, will run through the release logic, but neither create + DryRun bool `protobuf:"varint,4,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` } func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} } @@ -225,8 +233,23 @@ func (m *UpdateReleaseRequest) String() string { return proto.Compact func (*UpdateReleaseRequest) ProtoMessage() {} func (*UpdateReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } +func (m *UpdateReleaseRequest) GetChart() *hapi_chart3.Chart { + if m != nil { + return m.Chart + } + return nil +} + +func (m *UpdateReleaseRequest) GetValues() *hapi_chart.Config { + if m != nil { + return m.Values + } + return nil +} + // UpdateReleaseResponse is the response to an update request. type UpdateReleaseResponse struct { + Release *hapi_release3.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` } func (m *UpdateReleaseResponse) Reset() { *m = UpdateReleaseResponse{} } @@ -234,6 +257,13 @@ func (m *UpdateReleaseResponse) String() string { return proto.Compac func (*UpdateReleaseResponse) ProtoMessage() {} func (*UpdateReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } +func (m *UpdateReleaseResponse) GetRelease() *hapi_release3.Release { + if m != nil { + return m.Release + } + return nil +} + // InstallReleaseRequest is the request for an installation of a chart. type InstallReleaseRequest struct { // Chart is the protobuf representation of a chart. From 5e654c0380a6ef4998f3e5cc4af94be6713d2b03 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Fri, 8 Jul 2016 16:34:53 -0600 Subject: [PATCH 3/7] feat(*): get helm & tiller chatting about upgrades --- cmd/helm/upgrade.go | 45 ++++++++++++++++++++++--- pkg/helm/client.go | 18 ++++++---- pkg/helm/compat.go | 82 +++++++++++++++++++++++++++++++++++++++++++++ pkg/helm/option.go | 7 ++-- 4 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 pkg/helm/compat.go diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 5b14d9f76..ba6990ac4 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -20,6 +20,8 @@ import ( "fmt" "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" ) const upgradeDesc = ` @@ -30,17 +32,50 @@ argument can be a relative path to a packaged or unpackaged chart. ` var upgradeCmd = &cobra.Command{ - Use: "upgrade [RELEASE] [CHART]", - Short: "upgrade a release", - Long: upgradeDesc, - RunE: runUpgrade, + Use: "upgrade [RELEASE] [CHART]", + Short: "upgrade a release", + Long: upgradeDesc, + RunE: runUpgrade, + PersistentPreRunE: setupConnection, } +// upgrade flags +var ( + // upgradeDryRun performs a dry-run upgrade + upgradeDryRun bool + // upgradeValues is the filename of supplied values. + upgradeValues string +) + func init() { + f := upgradeCmd.Flags() + f.StringVarP(&upgradeValues, "values", "f", "", "path to a values YAML file") + f.BoolVar(&upgradeDryRun, "dry-run", false, "simulate an upgrade") + RootCommand.AddCommand(upgradeCmd) } func runUpgrade(cmd *cobra.Command, args []string) error { - fmt.Println("Coming Soon") + if err := checkArgsLength(2, len(args), "release name, chart path"); err != nil { + return err + } + + chartPath, err := locateChartPath(args[1]) + if err != nil { + return err + } + + rawVals, err := vals(upgradeValues) + if err != nil { + return err + } + + _, err = helm.UpdateRelease(args[0], chartPath, rawVals, upgradeDryRun) + if err != nil { + return prettyError(err) + } + + fmt.Println("Coming SOON to a Helm near YOU!") + return nil } diff --git a/pkg/helm/client.go b/pkg/helm/client.go index 6903560ed..3c9cdfe71 100644 --- a/pkg/helm/client.go +++ b/pkg/helm/client.go @@ -17,10 +17,12 @@ limitations under the License. package helm // import "k8s.io/helm/pkg/helm" import ( + "os" + "google.golang.org/grpc" + "k8s.io/helm/pkg/chartutil" rls "k8s.io/helm/pkg/proto/hapi/services" - "os" ) const ( @@ -102,18 +104,20 @@ func (h *Client) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.Unins return h.opts.rpcDeleteRelease(rlsName, rls.NewReleaseServiceClient(c), opts...) } -// UpdateRelease updates a release to a new/different chart. -// -// Note: there aren't currently any supported UpdateOptions, but they -// are kept in the API signature as a placeholder for future additions. -func (h *Client) UpdateRelease(rlsName string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { +// UpdateRelease updates a release to a new/different chart +func (h *Client) UpdateRelease(rlsName string, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) if err != nil { return nil, err } defer c.Close() - return h.opts.rpcUpdateRelease(rlsName, rls.NewReleaseServiceClient(c), opts...) + chart, err := chartutil.Load(chStr) + if err != nil { + return nil, err + } + + return h.opts.rpcUpdateRelease(rlsName, chart, rls.NewReleaseServiceClient(c), opts...) } // ReleaseStatus returns the given release's status. diff --git a/pkg/helm/compat.go b/pkg/helm/compat.go new file mode 100644 index 000000000..fcbea3d0f --- /dev/null +++ b/pkg/helm/compat.go @@ -0,0 +1,82 @@ +/* +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 5bafac62e..5fc5df066 100644 --- a/pkg/helm/option.go +++ b/pkg/helm/option.go @@ -17,7 +17,6 @@ limitations under the License. package helm import ( - "fmt" "golang.org/x/net/context" cpb "k8s.io/helm/pkg/proto/hapi/chart" rls "k8s.io/helm/pkg/proto/hapi/services" @@ -209,8 +208,10 @@ func (o *options) rpcDeleteRelease(rlsName string, rlc rls.ReleaseServiceClient, } // Executes tiller.UpdateRelease RPC. -func (o *options) rpcUpdateRelease(rlsName string, rlc rls.ReleaseServiceClient, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { - return nil, fmt.Errorf("helm: UpdateRelease: not implemented") +func (o *options) rpcUpdateRelease(rlsName string, chr *cpb.Chart, rlc rls.ReleaseServiceClient, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { + //TODO: handle dryRun + + return rlc.UpdateRelease(context.TODO(), &rls.UpdateReleaseRequest{Name: rlsName, Chart: chr}) } // Executes tiller.GetReleaseStatus RPC. From 0fcd7fccea033f2ef36e52bae7dcd4d3c916d059 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Fri, 8 Jul 2016 16:36:10 -0600 Subject: [PATCH 4/7] feat(tiller): add upgrade validations to tiller --- cmd/helm/upgrade.go | 7 ++++-- cmd/tiller/release_server.go | 49 +++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index ba6990ac4..ea499dd87 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -70,12 +70,15 @@ func runUpgrade(cmd *cobra.Command, args []string) error { return err } - _, err = helm.UpdateRelease(args[0], chartPath, rawVals, upgradeDryRun) + res, err := helm.UpdateRelease(args[0], chartPath, rawVals, upgradeDryRun) if err != nil { return prettyError(err) } - fmt.Println("Coming SOON to a Helm near YOU!") + newVersion := res.GetRelease().Version + fmt.Printf("I know you want to upgrade your release to version %v.\nHang tight. We're still working on the helm upgrade command.", newVersion) + + fmt.Println("\nComing soon to a Helm near YOU!") return nil } diff --git a/cmd/tiller/release_server.go b/cmd/tiller/release_server.go index 4dab3952d..e1f65e770 100644 --- a/cmd/tiller/release_server.go +++ b/cmd/tiller/release_server.go @@ -174,7 +174,54 @@ func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleas } func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { - return nil, errNotImplemented + rel, err := s.prepareUpdate(req) + if err != nil { + return nil, err + } + + // TODO: perform update + + return &services.UpdateReleaseResponse{Release: rel}, nil +} + +// prepareUpdate builds a release for an update operation. +func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, error) { + if req.Name == "" { + return nil, errMissingRelease + } + + if req.Chart == nil { + return nil, errMissingChart + } + + // finds the non-deleted release with the given name + rel, err := s.env.Releases.Read(req.Name) + if err != nil { + return nil, err + } + + //validate chart name is same as previous release + givenChart := req.Chart.Metadata.Name + releasedChart := rel.Chart.Metadata.Name + if givenChart != releasedChart { + return nil, fmt.Errorf("Given chart, %s, does not match chart originally released, %s", givenChart, releasedChart) + } + + // validate new chart version is higher than old + givenChartVersion := req.Chart.Metadata.Version + releasedChartVersion := rel.Chart.Metadata.Version + if givenChartVersion <= releasedChartVersion { + return nil, fmt.Errorf("Given chart (%s-%v) must be a higher version than released chart (%s-%v)", givenChart, givenChartVersion, releasedChart, releasedChartVersion) + } + + // Store an updated release. + updatedRelease := &release.Release{ + Name: req.Name, + Chart: req.Chart, + Config: req.Values, + Version: rel.Version + 1, + } + return updatedRelease, nil } func (s *releaseServer) uniqName(start string) (string, error) { From 36699cc22dff97482ffc7722b2116f27d4e38ffb Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Sun, 17 Jul 2016 14:47:02 -0600 Subject: [PATCH 5/7] ref(helm): refactor cmd/upgrade for unit testing --- cmd/helm/helm.go | 1 + cmd/helm/upgrade.go | 65 ++++++++++++++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 737c621b4..c47a13e6a 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -90,6 +90,7 @@ func newRootCmd(out io.Writer) *cobra.Command { newInstallCmd(nil, out), newDeleteCmd(nil, out), newInspectCmd(nil, out), + newUpgradeCmd(nil, out), ) return cmd } diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index ea499dd87..639598abd 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -18,6 +18,7 @@ package main import ( "fmt" + "io" "github.com/spf13/cobra" @@ -31,14 +32,6 @@ The upgrade arguments must be a release and a chart. The chart argument can be a relative path to a packaged or unpackaged chart. ` -var upgradeCmd = &cobra.Command{ - Use: "upgrade [RELEASE] [CHART]", - Short: "upgrade a release", - Long: upgradeDesc, - RunE: runUpgrade, - PersistentPreRunE: setupConnection, -} - // upgrade flags var ( // upgradeDryRun performs a dry-run upgrade @@ -47,20 +40,49 @@ var ( upgradeValues string ) -func init() { - f := upgradeCmd.Flags() +type upgradeCmd struct { + release string + chart string + out io.Writer + client helm.Interface +} + +func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { + + upgrade := &upgradeCmd{ + out: out, + client: client, + } + + cmd := &cobra.Command{ + Use: "upgrade [RELEASE] [CHART]", + Short: "upgrade a release", + Long: upgradeDesc, + PersistentPreRunE: setupConnection, + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkArgsLength(2, len(args), "release name, chart path"); err != nil { + return err + } + + upgrade.release = args[0] + upgrade.chart = args[1] + + 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") - RootCommand.AddCommand(upgradeCmd) + return cmd } -func runUpgrade(cmd *cobra.Command, args []string) error { - if err := checkArgsLength(2, len(args), "release name, chart path"); err != nil { - return err - } - - chartPath, err := locateChartPath(args[1]) +func (u *upgradeCmd) run() error { + chartPath, err := locateChartPath(u.chart) if err != nil { return err } @@ -70,15 +92,14 @@ func runUpgrade(cmd *cobra.Command, args []string) error { return err } - res, err := helm.UpdateRelease(args[0], chartPath, rawVals, upgradeDryRun) + _, err = helm.UpdateRelease(u.release, chartPath, rawVals, upgradeDryRun) if err != nil { return prettyError(err) } - newVersion := res.GetRelease().Version - fmt.Printf("I know you want to upgrade your release to version %v.\nHang tight. We're still working on the helm upgrade command.", newVersion) - - fmt.Println("\nComing soon to a Helm near YOU!") + 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") return nil + } From f74720613b56f3e7c684bbebbfaecd34a60eb488 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Tue, 19 Jul 2016 17:23:36 -0600 Subject: [PATCH 6/7] chore(helm): add client test for upgrade cmd I edited releaseMock as part of this PR --- cmd/helm/delete_test.go | 4 +- cmd/helm/get_hooks_test.go | 2 +- cmd/helm/get_manifest_test.go | 2 +- cmd/helm/get_test.go | 2 +- cmd/helm/get_values_test.go | 2 +- cmd/helm/helm_test.go | 45 ++++++++++++++----- cmd/helm/install_test.go | 6 +-- cmd/helm/list_test.go | 4 +- cmd/helm/upgrade.go | 41 ++++++++---------- cmd/helm/upgrade_test.go | 73 +++++++++++++++++++++++++++++++ pkg/helm/compat.go | 82 ----------------------------------- pkg/helm/option.go | 20 ++++++++- 12 files changed, 155 insertions(+), 128 deletions(-) create mode 100644 cmd/helm/upgrade_test.go delete mode 100644 pkg/helm/compat.go 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 From 62667e9f12df47fab174c0832c27974c45c9faa0 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Mon, 25 Jul 2016 12:06:27 -0600 Subject: [PATCH 7/7] ref(tiller): fix chart version comparison --- cmd/helm/upgrade_test.go | 3 +++ cmd/tiller/release_server.go | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go index 145ab28b1..341670cd8 100644 --- a/cmd/helm/upgrade_test.go +++ b/cmd/helm/upgrade_test.go @@ -53,6 +53,9 @@ func TestUpgradeCmd(t *testing.T) { Version: "0.1.2", } chartPath, err = chartutil.Create(cfile, tmpChart) + if err != nil { + t.Errorf("Error creating chart: %v", err) + } ch, _ = chartutil.Load(chartPath) tests := []releaseCase{ diff --git a/cmd/tiller/release_server.go b/cmd/tiller/release_server.go index e1f65e770..0497fe4fa 100644 --- a/cmd/tiller/release_server.go +++ b/cmd/tiller/release_server.go @@ -26,6 +26,7 @@ import ( "sort" "strings" + "github.com/Masterminds/semver" "github.com/ghodss/yaml" "github.com/technosophos/moniker" ctx "golang.org/x/net/context" @@ -49,8 +50,6 @@ func init() { } var ( - // errNotImplemented is a temporary error for uninmplemented callbacks. - errNotImplemented = errors.New("not implemented") // errMissingChart indicates that a chart was not provided. errMissingChart = errors.New("no chart provided") // errMissingRelease indicates that a release (name) was not provided. @@ -208,9 +207,20 @@ func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele } // validate new chart version is higher than old + givenChartVersion := req.Chart.Metadata.Version releasedChartVersion := rel.Chart.Metadata.Version - if givenChartVersion <= releasedChartVersion { + c, err := semver.NewConstraint("> " + releasedChartVersion) + if err != nil { + return nil, err + } + + v, err := semver.NewVersion(givenChartVersion) + if err != nil { + return nil, err + } + + if a := c.Check(v); !a { return nil, fmt.Errorf("Given chart (%s-%v) must be a higher version than released chart (%s-%v)", givenChart, givenChartVersion, releasedChart, releasedChartVersion) }