feat(tiller): add update logic to release server

pull/1030/head
Michelle Noorali 9 years ago
parent 9465ce979b
commit 8be3a34ac6

@ -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.

@ -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()

Loading…
Cancel
Save