[fake] implement rendering and simulated upgrades

pull/4358/head
Mike Lundy 6 years ago
parent 4139a00e17
commit 0a9c16f42b

@ -17,15 +17,20 @@ limitations under the License.
package helm // import "k8s.io/helm/pkg/helm" package helm // import "k8s.io/helm/pkg/helm"
import ( import (
"bytes"
"errors" "errors"
"math/rand" "math/rand"
"strings"
"sync" "sync"
"github.com/golang/protobuf/ptypes/timestamp" "github.com/golang/protobuf/ptypes/timestamp"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/manifest"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
rls "k8s.io/helm/pkg/proto/hapi/services" rls "k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/proto/hapi/version" "k8s.io/helm/pkg/proto/hapi/version"
"k8s.io/helm/pkg/renderutil"
storage "k8s.io/helm/pkg/storage/driver" storage "k8s.io/helm/pkg/storage/driver"
) )
@ -34,6 +39,7 @@ type FakeClient struct {
Rels []*release.Release Rels []*release.Release
Responses map[string]release.TestRun_Status Responses map[string]release.TestRun_Status
Opts options Opts options
RenderManifests bool
} }
// Option returns the fake release client // Option returns the fake release client
@ -96,7 +102,22 @@ func (c *FakeClient) InstallReleaseFromChart(chart *chart.Chart, ns string, opts
return nil, errors.New("cannot re-use a name that is still in use") return nil, errors.New("cannot re-use a name that is still in use")
} }
release := ReleaseMock(&MockReleaseOptions{Name: releaseName, Namespace: ns, Description: releaseDescription}) mockOpts := &MockReleaseOptions{
Name: releaseName,
Chart: chart,
Config: c.Opts.instReq.Values,
Namespace: ns,
Description: releaseDescription,
}
release := ReleaseMock(mockOpts)
if c.RenderManifests {
if err := RenderReleaseMock(release, false); err != nil {
return nil, err
}
}
if !c.Opts.dryRun { if !c.Opts.dryRun {
c.Rels = append(c.Rels, release) c.Rels = append(c.Rels, release)
} }
@ -135,14 +156,44 @@ func (c *FakeClient) UpdateRelease(rlsName string, chStr string, opts ...UpdateO
} }
// UpdateReleaseFromChart returns an UpdateReleaseResponse containing the updated release, if it exists // UpdateReleaseFromChart returns an UpdateReleaseResponse containing the updated release, if it exists
func (c *FakeClient) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { func (c *FakeClient) UpdateReleaseFromChart(rlsName string, newChart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
for _, opt := range opts {
opt(&c.Opts)
}
// Check to see if the release already exists. // Check to see if the release already exists.
rel, err := c.ReleaseContent(rlsName, nil) rel, err := c.ReleaseContent(rlsName, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &rls.UpdateReleaseResponse{Release: rel.Release}, nil mockOpts := &MockReleaseOptions{
Name: rel.Release.Name,
Version: rel.Release.Version + 1,
Chart: newChart,
Config: c.Opts.updateReq.Values,
Namespace: rel.Release.Namespace,
Description: c.Opts.updateReq.Description,
}
newRelease := ReleaseMock(mockOpts)
if c.Opts.updateReq.ResetValues {
newRelease.Config = &chart.Config{Raw: "{}"}
} else if c.Opts.updateReq.ReuseValues {
// TODO: This should merge old and new values but does not.
}
if c.RenderManifests {
if err := RenderReleaseMock(newRelease, true); err != nil {
return nil, err
}
}
if !c.Opts.dryRun {
*rel.Release = *newRelease
}
return &rls.UpdateReleaseResponse{Release: newRelease}, nil
} }
// RollbackRelease returns nil, nil // RollbackRelease returns nil, nil
@ -231,12 +282,15 @@ type MockReleaseOptions struct {
Name string Name string
Version int32 Version int32
Chart *chart.Chart Chart *chart.Chart
Config *chart.Config
StatusCode release.Status_Code StatusCode release.Status_Code
Namespace string Namespace string
Description string Description string
} }
// ReleaseMock creates a mock release object based on options set by MockReleaseOptions. This function should typically not be used outside of testing. // ReleaseMock creates a mock release object based on options set by
// MockReleaseOptions. This function should typically not be used outside of
// testing.
func ReleaseMock(opts *MockReleaseOptions) *release.Release { func ReleaseMock(opts *MockReleaseOptions) *release.Release {
date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0}
@ -273,6 +327,11 @@ func ReleaseMock(opts *MockReleaseOptions) *release.Release {
} }
} }
config := opts.Config
if config == nil {
config = &chart.Config{Raw: `name: "value"`}
}
scode := release.Status_DEPLOYED scode := release.Status_DEPLOYED
if opts.StatusCode > 0 { if opts.StatusCode > 0 {
scode = opts.StatusCode scode = opts.StatusCode
@ -287,7 +346,7 @@ func ReleaseMock(opts *MockReleaseOptions) *release.Release {
Description: description, Description: description,
}, },
Chart: ch, Chart: ch,
Config: &chart.Config{Raw: `name: "value"`}, Config: config,
Version: version, Version: version,
Namespace: namespace, Namespace: namespace,
Hooks: []*release.Hook{ Hooks: []*release.Hook{
@ -303,3 +362,39 @@ func ReleaseMock(opts *MockReleaseOptions) *release.Release {
Manifest: MockManifest, Manifest: MockManifest,
} }
} }
// RenderReleaseMock will take a release (usually produced by helm.ReleaseMock)
// and will render the Manifest inside using the local mechanism (no tiller).
// (Compare to renderResources in pkg/tiller)
func RenderReleaseMock(r *release.Release, asUpgrade bool) error {
if r == nil || r.Chart == nil || r.Chart.Metadata == nil {
return errors.New("a release with a chart with metadata must be provided to render the manifests")
}
renderOpts := renderutil.Options{
ReleaseOptions: chartutil.ReleaseOptions{
Name: r.Name,
Namespace: r.Namespace,
Time: r.Info.LastDeployed,
Revision: int(r.Version),
IsUpgrade: asUpgrade,
IsInstall: !asUpgrade,
},
}
rendered, err := renderutil.Render(r.Chart, r.Config, renderOpts)
if err != nil {
return err
}
b := bytes.NewBuffer(nil)
for _, m := range manifest.SplitManifests(rendered) {
// Remove empty manifests
if len(strings.TrimSpace(m.Content)) == 0 {
continue
}
b.WriteString("\n---\n# Source: " + m.Name + "\n")
b.WriteString(m.Content)
}
r.Manifest = b.String()
return nil
}

@ -17,6 +17,7 @@ limitations under the License.
package helm package helm
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
@ -25,6 +26,57 @@ import (
rls "k8s.io/helm/pkg/proto/hapi/services" rls "k8s.io/helm/pkg/proto/hapi/services"
) )
const cmInputTemplate = `kind: ConfigMap
apiVersion: v1
metadata:
name: example
data:
Release:
{{.Release | toYaml | indent 4}}
`
const cmOutputTemplate = `
---
# Source: installChart/templates/cm.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: example
data:
Release:
IsInstall: %t
IsUpgrade: %t
Name: new-release
Namespace: default
Revision: %d
Service: Tiller
Time:
seconds: 242085845
`
var installChart *chart.Chart
func init() {
installChart = &chart.Chart{
Metadata: &chart.Metadata{Name: "installChart"},
Templates: []*chart.Template{
{Name: "templates/cm.yaml", Data: []byte(cmInputTemplate)},
},
}
}
func releaseWithChart(opts *MockReleaseOptions) *release.Release {
if opts.Chart == nil {
opts.Chart = installChart
}
return ReleaseMock(opts)
}
func withManifest(r *release.Release, isUpgrade bool) *release.Release {
r.Manifest = fmt.Sprintf(cmOutputTemplate, !isUpgrade, isUpgrade, r.Version)
return r
}
func TestFakeClient_ReleaseStatus(t *testing.T) { func TestFakeClient_ReleaseStatus(t *testing.T) {
releasePresent := ReleaseMock(&MockReleaseOptions{Name: "release-present"}) releasePresent := ReleaseMock(&MockReleaseOptions{Name: "release-present"})
releaseNotPresent := ReleaseMock(&MockReleaseOptions{Name: "release-not-present"}) releaseNotPresent := ReleaseMock(&MockReleaseOptions{Name: "release-not-present"})
@ -117,9 +169,9 @@ func TestFakeClient_ReleaseStatus(t *testing.T) {
} }
func TestFakeClient_InstallReleaseFromChart(t *testing.T) { func TestFakeClient_InstallReleaseFromChart(t *testing.T) {
installChart := &chart.Chart{}
type fields struct { type fields struct {
Rels []*release.Release Rels []*release.Release
RenderManifests bool
} }
type args struct { type args struct {
ns string ns string
@ -143,10 +195,10 @@ func TestFakeClient_InstallReleaseFromChart(t *testing.T) {
opts: []InstallOption{ReleaseName("new-release")}, opts: []InstallOption{ReleaseName("new-release")},
}, },
want: &rls.InstallReleaseResponse{ want: &rls.InstallReleaseResponse{
Release: ReleaseMock(&MockReleaseOptions{Name: "new-release"}), Release: releaseWithChart(&MockReleaseOptions{Name: "new-release"}),
}, },
relsAfter: []*release.Release{ relsAfter: []*release.Release{
ReleaseMock(&MockReleaseOptions{Name: "new-release"}), releaseWithChart(&MockReleaseOptions{Name: "new-release"}),
}, },
wantErr: false, wantErr: false,
}, },
@ -160,10 +212,10 @@ func TestFakeClient_InstallReleaseFromChart(t *testing.T) {
opts: []InstallOption{ReleaseName("new-release"), InstallDescription("foo-bar")}, opts: []InstallOption{ReleaseName("new-release"), InstallDescription("foo-bar")},
}, },
want: &rls.InstallReleaseResponse{ want: &rls.InstallReleaseResponse{
Release: ReleaseMock(&MockReleaseOptions{Name: "new-release", Description: "foo-bar"}), Release: releaseWithChart(&MockReleaseOptions{Name: "new-release", Description: "foo-bar"}),
}, },
relsAfter: []*release.Release{ relsAfter: []*release.Release{
ReleaseMock(&MockReleaseOptions{Name: "new-release", Description: "foo-bar"}), releaseWithChart(&MockReleaseOptions{Name: "new-release", Description: "foo-bar"}),
}, },
wantErr: false, wantErr: false,
}, },
@ -171,7 +223,7 @@ func TestFakeClient_InstallReleaseFromChart(t *testing.T) {
name: "Try to add a release where the name already exists.", name: "Try to add a release where the name already exists.",
fields: fields{ fields: fields{
Rels: []*release.Release{ Rels: []*release.Release{
ReleaseMock(&MockReleaseOptions{Name: "new-release"}), releaseWithChart(&MockReleaseOptions{Name: "new-release"}),
}, },
}, },
args: args{ args: args{
@ -179,16 +231,35 @@ func TestFakeClient_InstallReleaseFromChart(t *testing.T) {
opts: []InstallOption{ReleaseName("new-release")}, opts: []InstallOption{ReleaseName("new-release")},
}, },
relsAfter: []*release.Release{ relsAfter: []*release.Release{
ReleaseMock(&MockReleaseOptions{Name: "new-release"}), releaseWithChart(&MockReleaseOptions{Name: "new-release"}),
}, },
want: nil, want: nil,
wantErr: true, wantErr: true,
}, },
{
name: "Render the given chart.",
fields: fields{
Rels: []*release.Release{},
RenderManifests: true,
},
args: args{
ns: "default",
opts: []InstallOption{ReleaseName("new-release")},
},
want: &rls.InstallReleaseResponse{
Release: withManifest(releaseWithChart(&MockReleaseOptions{Name: "new-release"}), false),
},
relsAfter: []*release.Release{
withManifest(releaseWithChart(&MockReleaseOptions{Name: "new-release"}), false),
},
wantErr: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &FakeClient{ c := &FakeClient{
Rels: tt.fields.Rels, Rels: tt.fields.Rels,
RenderManifests: tt.fields.RenderManifests,
} }
got, err := c.InstallReleaseFromChart(installChart, tt.args.ns, tt.args.opts...) got, err := c.InstallReleaseFromChart(installChart, tt.args.ns, tt.args.opts...)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
@ -293,7 +364,84 @@ func TestFakeClient_DeleteRelease(t *testing.T) {
} }
if !reflect.DeepEqual(c.Rels, tt.relsAfter) { if !reflect.DeepEqual(c.Rels, tt.relsAfter) {
t.Errorf("FakeClient.InstallReleaseFromChart() rels = %v, expected %v", got, tt.relsAfter) t.Errorf("FakeClient.InstallReleaseFromChart() rels = %v, expected %v", c.Rels, tt.relsAfter)
}
})
}
}
func TestFakeClient_UpdateReleaseFromChart(t *testing.T) {
type fields struct {
Rels []*release.Release
RenderManifests bool
}
type args struct {
release string
opts []UpdateOption
}
tests := []struct {
name string
fields fields
args args
want *rls.UpdateReleaseResponse
relsAfter []*release.Release
wantErr bool
}{
{
name: "Update release.",
fields: fields{
Rels: []*release.Release{
releaseWithChart(&MockReleaseOptions{Name: "new-release"}),
},
},
args: args{
release: "new-release",
opts: []UpdateOption{},
},
want: &rls.UpdateReleaseResponse{
Release: releaseWithChart(&MockReleaseOptions{Name: "new-release", Version: 2}),
},
relsAfter: []*release.Release{
releaseWithChart(&MockReleaseOptions{Name: "new-release", Version: 2}),
},
},
{
name: "Update and render given chart.",
fields: fields{
Rels: []*release.Release{
releaseWithChart(&MockReleaseOptions{Name: "new-release"}),
},
RenderManifests: true,
},
args: args{
release: "new-release",
opts: []UpdateOption{},
},
want: &rls.UpdateReleaseResponse{
Release: withManifest(releaseWithChart(&MockReleaseOptions{Name: "new-release", Version: 2}), true),
},
relsAfter: []*release.Release{
withManifest(releaseWithChart(&MockReleaseOptions{Name: "new-release", Version: 2}), true),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &FakeClient{
Rels: tt.fields.Rels,
RenderManifests: tt.fields.RenderManifests,
}
got, err := c.UpdateReleaseFromChart(tt.args.release, installChart, tt.args.opts...)
if (err != nil) != tt.wantErr {
t.Errorf("FakeClient.UpdateReleaseFromChart() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("FakeClient.UpdateReleaseFromChart() =\n%v\nwant\n%v", got, tt.want)
}
if !reflect.DeepEqual(c.Rels, tt.relsAfter) {
t.Errorf("FakeClient.UpdateReleaseFromChart() rels =\n%v\nwant\n%v", c.Rels, tt.relsAfter)
} }
}) })
} }

Loading…
Cancel
Save