diff --git a/cmd/tiller/release_server.go b/cmd/tiller/release_server.go index 061a76c4b..afe13376e 100644 --- a/cmd/tiller/release_server.go +++ b/cmd/tiller/release_server.go @@ -24,7 +24,6 @@ import ( "regexp" "sort" - "github.com/Masterminds/semver" "github.com/ghodss/yaml" "github.com/technosophos/moniker" ctx "golang.org/x/net/context" @@ -171,65 +170,107 @@ func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleas } func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { - rel, err := s.prepareUpdate(req) + currentRelease, updatedRelease, err := s.prepareUpdate(req) if err != nil { return nil, err } - // TODO: perform update + res, err := s.performUpdate(currentRelease, updatedRelease, req) + if err != nil { + return nil, err + } + + if err := s.env.Releases.Update(updatedRelease); err != nil { + return nil, err + } + + return res, nil +} + +func (s *releaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { + res := &services.UpdateReleaseResponse{Release: updatedRelease} + + if req.DryRun { + log.Printf("Dry run for %s", updatedRelease.Name) + return res, nil + } + + // pre-ugrade hooks + //if !req.DisableHooks { + //if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, preUpgrade); err != nil { + //return res, err + //} + //} + + kubeCli := s.env.KubeClient + original := bytes.NewBufferString(originalRelease.Manifest) + modified := bytes.NewBufferString(updatedRelease.Manifest) + if err := kubeCli.Update(updatedRelease.Namespace, original, modified); err != nil { + return nil, fmt.Errorf("Update of %s failed: %s", updatedRelease.Name, err) + } + + // post-upgrade hooks + //if !req.DisableHooks { + //if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, postUpgrade); err != nil { + //return res, err + //} + //} - return &services.UpdateReleaseResponse{Release: rel}, nil + updatedRelease.Info.Status.Code = release.Status_DEPLOYED + + return res, nil } -// prepareUpdate builds a release for an update operation. -func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, error) { +// prepareUpdate builds an updated release for an update operation. +func (s *releaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) { if req.Name == "" { - return nil, errMissingRelease + return nil, nil, errMissingRelease } if req.Chart == nil { - return nil, errMissingChart + return nil, nil, errMissingChart } // finds the non-deleted release with the given name - rel, err := s.env.Releases.Read(req.Name) + currentRelease, err := s.env.Releases.Read(req.Name) if err != nil { - return nil, err + return nil, 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) + ts := timeconv.Now() + options := chartutil.ReleaseOptions{ + Name: req.Name, + Time: ts, + Namespace: currentRelease.Namespace, } - // validate new chart version is higher than old - - givenChartVersion := req.Chart.Metadata.Version - releasedChartVersion := rel.Chart.Metadata.Version - c, err := semver.NewConstraint("> " + releasedChartVersion) + valuesToRender, err := chartutil.ToRenderValues(req.Chart, req.Values, options) if err != nil { - return nil, err + return nil, nil, err } - v, err := semver.NewVersion(givenChartVersion) + hooks, manifestDoc, err := s.renderResources(req.Chart, valuesToRender) 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) + return nil, nil, err } // Store an updated release. updatedRelease := &release.Release{ - Name: req.Name, - Chart: req.Chart, - Config: req.Values, - Version: rel.Version + 1, + Name: req.Name, + Namespace: currentRelease.Namespace, + Chart: req.Chart, + Config: req.Values, + Info: &release.Info{ + FirstDeployed: currentRelease.Info.FirstDeployed, + LastDeployed: ts, + Status: &release.Status{Code: release.Status_UNKNOWN}, + }, + Version: currentRelease.Version + 1, + Manifest: manifestDoc.String(), + Hooks: hooks, } - return updatedRelease, nil + + return currentRelease, updatedRelease, nil } func (s *releaseServer) uniqName(start string, reuse bool) (string, error) { @@ -308,12 +349,36 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re return nil, err } - renderer := s.engine(req.Chart) - files, err := renderer.Render(req.Chart, valuesToRender) + hooks, manifestDoc, err := s.renderResources(req.Chart, valuesToRender) if err != nil { return nil, err } + // Store a release. + rel := &release.Release{ + Name: name, + Namespace: req.Namespace, + Chart: req.Chart, + Config: req.Values, + Info: &release.Info{ + FirstDeployed: ts, + LastDeployed: ts, + Status: &release.Status{Code: release.Status_UNKNOWN}, + }, + Manifest: manifestDoc.String(), + Hooks: hooks, + Version: 1, + } + return rel, nil +} + +func (s *releaseServer) renderResources(ch *chart.Chart, values chartutil.Values) ([]*release.Hook, *bytes.Buffer, error) { + renderer := s.engine(ch) + files, err := renderer.Render(ch, values) + if err != nil { + return nil, nil, err + } + // Sort hooks, manifests, and partials. Only hooks and manifests are returned, // as partials are not used after renderer.Render. Empty manifests are also // removed here. @@ -321,7 +386,7 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re if err != nil { // By catching parse errors here, we can prevent bogus releases from going // to Kubernetes. - return nil, err + return nil, nil, err } // Aggregate all valid manifests into one big doc. @@ -331,22 +396,7 @@ func (s *releaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re b.WriteString(file) } - // Store a release. - rel := &release.Release{ - Name: name, - Namespace: req.Namespace, - Chart: req.Chart, - Config: req.Values, - Info: &release.Info{ - FirstDeployed: ts, - LastDeployed: ts, - Status: &release.Status{Code: release.Status_UNKNOWN}, - }, - Manifest: b.String(), - Hooks: hooks, - Version: 1, - } - return rel, nil + return hooks, b, nil } // validateYAML checks to see if YAML is well-formed. diff --git a/cmd/tiller/release_server_test.go b/cmd/tiller/release_server_test.go index 18c36bd78..a6e6c2fa8 100644 --- a/cmd/tiller/release_server_test.go +++ b/cmd/tiller/release_server_test.go @@ -79,8 +79,9 @@ func releaseStub() *release.Release { LastDeployed: &date, Status: &release.Status{Code: release.Status_DEPLOYED}, }, - Chart: chartStub(), - Config: &chart.Config{Raw: `name = "value"`}, + Chart: chartStub(), + Config: &chart.Config{Raw: `name = "value"`}, + Version: 1, Hooks: []*release.Hook{ { Name: "test-cm", @@ -289,6 +290,59 @@ func TestInstallReleaseReuseName(t *testing.T) { } } +func TestUpdateRelease(t *testing.T) { + c := context.Background() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "hello", Data: []byte("hello: world")}, + }, + }, + } + res, err := rs.UpdateRelease(c, req) + if err != nil { + t.Errorf("Failed updated: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + + if res.Release.Name != rel.Name { + t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name) + } + + if res.Release.Namespace != rel.Namespace { + t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) + } + + updated, err := rs.env.Releases.Read(res.Release.Name) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + if len(res.Release.Manifest) == 0 { + t.Errorf("No manifest returned: %v", res.Release) + } + + if len(updated.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if !strings.Contains(updated.Manifest, "---\n# Source: hello/hello\nhello: world") { + t.Errorf("unexpected output: %s", rel.Manifest) + } + + if res.Release.Version != 2 { + t.Errorf("Expected release version to be %v, got %v", 2, res.Release.Version) + } +} + func TestUninstallRelease(t *testing.T) { c := context.Background() rs := rsFixture()