-
Adam Reese authored
closes: #1395
4f4be2ec
/*
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 (
"errors"
"fmt"
"io"
"os"
"regexp"
"strings"
"testing"
"github.com/golang/protobuf/ptypes/timestamp"
"golang.org/x/net/context"
"google.golang.org/grpc/metadata"
"k8s.io/helm/cmd/tiller/environment"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/storage"
"k8s.io/helm/pkg/storage/driver"
)
const notesText = "my notes here"
var manifestWithHook = `apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
annotations:
"helm.sh/hook": post-install,pre-delete
data:
name: value
`
var manifestWithUpgradeHooks = `apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
annotations:
"helm.sh/hook": post-upgrade,pre-upgrade
data:
name: value
`
var manifestWithRollbackHooks = `apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
annotations:
"helm.sh/hook": post-rollback,pre-rollback
data:
name: value
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
`
func rsFixture() *releaseServer {
return &releaseServer{
env: mockEnvironment(),
}
}
// chartStub creates a fully stubbed out chart.
func chartStub() *chart.Chart {
return &chart.Chart{
// TODO: This should be more complete.
Metadata: &chart.Metadata{
Name: "hello",
},
// This adds basic templates, partials, and hooks.
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "goodbye", Data: []byte("goodbye: world")},
{Name: "empty", Data: []byte("")},
{Name: "with-partials", Data: []byte(`hello: {{ template "_planet" . }}`)},
{Name: "partials/_planet", Data: []byte(`{{define "_planet"}}Earth{{end}}`)},
{Name: "hooks", Data: []byte(manifestWithHook)},
},
}
}
// releaseStub creates a release stub, complete with the chartStub as its chart.
func releaseStub() *release.Release {
return namedReleaseStub("angry-panda", release.Status_DEPLOYED)
}
func namedReleaseStub(name string, status release.Status_Code) *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: status},
},
Chart: chartStub(),
Config: &chart.Config{Raw: `name: value`},
Version: 1,
Hooks: []*release.Hook{
{
Name: "test-cm",
Kind: "ConfigMap",
Path: "test-cm",
Manifest: manifestWithHook,
Events: []release.Hook_Event{
release.Hook_POST_INSTALL,
release.Hook_PRE_DELETE,
},
},
},
}
}
func upgradeReleaseVersion(rel *release.Release) *release.Release {
date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0}
rel.Info.Status.Code = release.Status_SUPERSEDED
return &release.Release{
Name: rel.Name,
Info: &release.Info{
FirstDeployed: rel.Info.FirstDeployed,
LastDeployed: &date,
Status: &release.Status{Code: release.Status_DEPLOYED},
},
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
Chart: rel.Chart,
Config: rel.Config,
Version: rel.Version + 1,
}
}
func TestGetVersionSet(t *testing.T) {
rs := rsFixture()
vs, err := rs.getVersionSet()
if err != nil {
t.Error(err)
}
if !vs.Has("v1") {
t.Errorf("Expected supported versions to at least include v1.")
}
if vs.Has("nosuchversion/v1") {
t.Error("Non-existent version is reported found.")
}
}
func TestUniqName(t *testing.T) {
rs := rsFixture()
rel1 := releaseStub()
rel2 := releaseStub()
rel2.Name = "happy-panda"
rel2.Info.Status.Code = release.Status_DELETED
rs.env.Releases.Create(rel1)
rs.env.Releases.Create(rel2)
tests := []struct {
name string
expect string
reuse bool
err bool
}{
{"first", "first", false, false},
{"", "[a-z]+-[a-z]+", false, false},
{"angry-panda", "", false, true},
{"happy-panda", "", false, true},
{"happy-panda", "happy-panda", true, false},
{"hungry-hungry-hippos", "", true, true}, // Exceeds max name length
}
for _, tt := range tests {
u, err := rs.uniqName(tt.name, tt.reuse)
if err != nil {
if tt.err {
continue
}
t.Fatal(err)
}
if tt.err {
t.Errorf("Expected an error for %q", tt.name)
}
if match, err := regexp.MatchString(tt.expect, u); err != nil {
t.Fatal(err)
} else if !match {
t.Errorf("Expected %q to match %q", u, tt.expect)
}
}
}
func TestInstallRelease(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
// TODO: Refactor this into a mock.
req := &services.InstallReleaseRequest{
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
Namespace: "spaced",
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "hooks", Data: []byte(manifestWithHook)},
},
},
}
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
if res.Release.Name == "" {
t.Errorf("Expected release name.")
}
if res.Release.Namespace != "spaced" {
t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace)
}
rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
t.Logf("rel: %v", rel)
if len(rel.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks))
}
if rel.Hooks[0].Manifest != manifestWithHook {
t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest)
}
if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL {
t.Errorf("Expected event 0 is post install")
}
if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE {
t.Errorf("Expected event 0 is pre-delete")
}
if len(res.Release.Manifest) == 0 {
t.Errorf("No manifest returned: %v", res.Release)
}
if len(rel.Manifest) == 0 {
t.Errorf("Expected manifest in %v", res)
}
if !strings.Contains(rel.Manifest, "---\n# Source: hello/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest)
}
}
func TestInstallReleaseWithNotes(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
// TODO: Refactor this into a mock.
req := &services.InstallReleaseRequest{
Namespace: "spaced",
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "hooks", Data: []byte(manifestWithHook)},
{Name: "NOTES.txt", Data: []byte(notesText)},
},
},
}
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
if res.Release.Name == "" {
t.Errorf("Expected release name.")
}
if res.Release.Namespace != "spaced" {
t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace)
}
rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
t.Logf("rel: %v", rel)
if len(rel.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks))
}
if rel.Hooks[0].Manifest != manifestWithHook {
t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest)
}
if rel.Info.Status.Notes != notesText {
t.Fatalf("Expected '%s', got '%s'", notesText, rel.Info.Status.Notes)
}
if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL {
t.Errorf("Expected event 0 is post install")
}
if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE {
t.Errorf("Expected event 0 is pre-delete")
}
if len(res.Release.Manifest) == 0 {
t.Errorf("No manifest returned: %v", res.Release)
}
if len(rel.Manifest) == 0 {
t.Errorf("Expected manifest in %v", res)
}
if !strings.Contains(rel.Manifest, "---\n# Source: hello/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest)
}
}
func TestInstallReleaseWithNotesRendered(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
// TODO: Refactor this into a mock.
req := &services.InstallReleaseRequest{
Namespace: "spaced",
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "hooks", Data: []byte(manifestWithHook)},
{Name: "NOTES.txt", Data: []byte(notesText + " {{.Release.Name}}")},
},
},
}
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
if res.Release.Name == "" {
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
t.Errorf("Expected release name.")
}
if res.Release.Namespace != "spaced" {
t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace)
}
rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
t.Logf("rel: %v", rel)
if len(rel.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks))
}
if rel.Hooks[0].Manifest != manifestWithHook {
t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest)
}
expectedNotes := fmt.Sprintf("%s %s", notesText, res.Release.Name)
if rel.Info.Status.Notes != expectedNotes {
t.Fatalf("Expected '%s', got '%s'", expectedNotes, rel.Info.Status.Notes)
}
if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL {
t.Errorf("Expected event 0 is post install")
}
if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE {
t.Errorf("Expected event 0 is pre-delete")
}
if len(res.Release.Manifest) == 0 {
t.Errorf("No manifest returned: %v", res.Release)
}
if len(rel.Manifest) == 0 {
t.Errorf("Expected manifest in %v", res)
}
if !strings.Contains(rel.Manifest, "---\n# Source: hello/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest)
}
}
func TestInstallReleaseDryRun(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
req := &services.InstallReleaseRequest{
Chart: chartStub(),
DryRun: true,
}
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Errorf("Failed install: %s", err)
}
if res.Release.Name == "" {
t.Errorf("Expected release name.")
}
if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/hello\nhello: world") {
t.Errorf("unexpected output: %s", res.Release.Manifest)
}
if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/goodbye\ngoodbye: world") {
t.Errorf("unexpected output: %s", res.Release.Manifest)
}
if !strings.Contains(res.Release.Manifest, "hello: Earth") {
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
t.Errorf("Should contain partial content. %s", res.Release.Manifest)
}
if strings.Contains(res.Release.Manifest, "hello: {{ template \"_planet\" . }}") {
t.Errorf("Should not contain partial templates itself. %s", res.Release.Manifest)
}
if strings.Contains(res.Release.Manifest, "empty") {
t.Errorf("Should not contain template data for an empty file. %s", res.Release.Manifest)
}
if _, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version); err == nil {
t.Errorf("Expected no stored release.")
}
if l := len(res.Release.Hooks); l != 1 {
t.Fatalf("Expected 1 hook, got %d", l)
}
if res.Release.Hooks[0].LastRun != nil {
t.Error("Expected hook to not be marked as run.")
}
}
func TestInstallReleaseNoHooks(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rs.env.Releases.Create(releaseStub())
req := &services.InstallReleaseRequest{
Chart: chartStub(),
DisableHooks: true,
}
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Errorf("Failed install: %s", err)
}
if hl := res.Release.Hooks[0].LastRun; hl != nil {
t.Errorf("Expected that no hooks were run. Got %d", hl)
}
}
func TestInstallReleaseFailedHooks(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rs.env.Releases.Create(releaseStub())
rs.env.KubeClient = newHookFailingKubeClient()
req := &services.InstallReleaseRequest{
Chart: chartStub(),
}
res, err := rs.InstallRelease(c, req)
if err == nil {
t.Error("Expected failed install")
}
if hl := res.Release.Info.Status.Code; hl != release.Status_FAILED {
t.Errorf("Expected FAILED release. Got %d", hl)
}
}
func TestInstallReleaseReuseName(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
rel.Info.Status.Code = release.Status_DELETED
rs.env.Releases.Create(rel)
req := &services.InstallReleaseRequest{
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
Chart: chartStub(),
ReuseName: true,
Name: rel.Name,
}
res, err := rs.InstallRelease(c, req)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
if res.Release.Name != rel.Name {
t.Errorf("expected %q, got %q", rel.Name, res.Release.Name)
}
getreq := &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1}
getres, err := rs.GetReleaseStatus(c, getreq)
if err != nil {
t.Errorf("Failed to retrieve release: %s", err)
}
if getres.Info.Status.Code != release.Status_DEPLOYED {
t.Errorf("Release status is %q", getres.Info.Status.Code)
}
}
func TestUpdateRelease(t *testing.T) {
c := helm.NewContext()
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")},
{Name: "hooks", Data: []byte(manifestWithUpgradeHooks)},
},
},
}
res, err := rs.UpdateRelease(c, req)
if err != nil {
t.Fatalf("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.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
if len(updated.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks))
}
if updated.Hooks[0].Manifest != manifestWithUpgradeHooks {
t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest)
}
if updated.Hooks[0].Events[0] != release.Hook_POST_UPGRADE {
t.Errorf("Expected event 0 to be post upgrade")
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
}
if updated.Hooks[0].Events[1] != release.Hook_PRE_UPGRADE {
t.Errorf("Expected event 0 to be pre upgrade")
}
if len(res.Release.Manifest) == 0 {
t.Errorf("No manifest returned: %v", res.Release)
}
if res.Release.Config == nil {
t.Errorf("Got release without config: %#v", res.Release)
} else if res.Release.Config.Raw != rel.Config.Raw {
t.Errorf("Expected release values %q, got %q", rel.Config.Raw, res.Release.Config.Raw)
}
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 TestUpdateReleaseFailure(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
rs.env.KubeClient = newUpdateFailingKubeClient()
req := &services.UpdateReleaseRequest{
Name: rel.Name,
DisableHooks: true,
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "something", Data: []byte("hello: world")},
},
},
}
res, err := rs.UpdateRelease(c, req)
if err == nil {
t.Error("Expected failed update")
}
if updatedStatus := res.Release.Info.Status.Code; updatedStatus != release.Status_FAILED {
t.Errorf("Expected FAILED release. Got %d", updatedStatus)
}
oldRelease, err := rs.env.Releases.Get(rel.Name, rel.Version)
if err != nil {
t.Errorf("Expected to be able to get previous release")
}
if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_SUPERSEDED {
t.Errorf("Expected SUPERSEDED status on previous Release version. Got %v", oldStatus)
}
}
func TestRollbackReleaseFailure(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
upgradedRel := upgradeReleaseVersion(rel)
rs.env.Releases.Update(rel)
rs.env.Releases.Create(upgradedRel)
req := &services.RollbackReleaseRequest{
Name: rel.Name,
DisableHooks: true,
}
rs.env.KubeClient = newUpdateFailingKubeClient()
res, err := rs.RollbackRelease(c, req)
if err == nil {
t.Error("Expected failed rollback")
}
if targetStatus := res.Release.Info.Status.Code; targetStatus != release.Status_FAILED {
t.Errorf("Expected FAILED release. Got %v", targetStatus)
}
oldRelease, err := rs.env.Releases.Get(rel.Name, rel.Version)
if err != nil {
t.Errorf("Expected to be able to get previous release")
}
if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_SUPERSEDED {
t.Errorf("Expected SUPERSEDED status on previous Release version. Got %v", oldStatus)
}
}
func TestUpdateReleaseNoHooks(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
req := &services.UpdateReleaseRequest{
Name: rel.Name,
DisableHooks: true,
Chart: &chart.Chart{
Metadata: &chart.Metadata{Name: "hello"},
Templates: []*chart.Template{
{Name: "hello", Data: []byte("hello: world")},
{Name: "hooks", Data: []byte(manifestWithUpgradeHooks)},
},
},
}
res, err := rs.UpdateRelease(c, req)
if err != nil {
t.Fatalf("Failed updated: %s", err)
}
if hl := res.Release.Hooks[0].LastRun; hl != nil {
t.Errorf("Expected that no hooks were run. Got %d", hl)
}
}
func TestUpdateReleaseNoChanges(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
req := &services.UpdateReleaseRequest{
Name: rel.Name,
DisableHooks: true,
Chart: rel.GetChart(),
}
_, err := rs.UpdateRelease(c, req)
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
if err != nil {
t.Fatalf("Failed updated: %s", err)
}
}
func TestRollbackReleaseNoHooks(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
rel.Hooks = []*release.Hook{
{
Name: "test-cm",
Kind: "ConfigMap",
Path: "test-cm",
Manifest: manifestWithRollbackHooks,
Events: []release.Hook_Event{
release.Hook_PRE_ROLLBACK,
release.Hook_POST_ROLLBACK,
},
},
}
rs.env.Releases.Create(rel)
upgradedRel := upgradeReleaseVersion(rel)
rs.env.Releases.Update(rel)
rs.env.Releases.Create(upgradedRel)
req := &services.RollbackReleaseRequest{
Name: rel.Name,
DisableHooks: true,
}
res, err := rs.RollbackRelease(c, req)
if err != nil {
t.Fatalf("Failed rollback: %s", err)
}
if hl := res.Release.Hooks[0].LastRun; hl != nil {
t.Errorf("Expected that no hooks were run. Got %d", hl)
}
}
func TestRollbackWithReleaseVersion(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
upgradedRel := upgradeReleaseVersion(rel)
rs.env.Releases.Update(rel)
rs.env.Releases.Create(upgradedRel)
req := &services.RollbackReleaseRequest{
Name: rel.Name,
DisableHooks: true,
Version: 1,
}
_, err := rs.RollbackRelease(c, req)
if err != nil {
t.Fatalf("Failed rollback: %s", err)
}
}
func TestRollbackRelease(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
upgradedRel := upgradeReleaseVersion(rel)
upgradedRel.Hooks = []*release.Hook{
{
771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
Name: "test-cm",
Kind: "ConfigMap",
Path: "test-cm",
Manifest: manifestWithRollbackHooks,
Events: []release.Hook_Event{
release.Hook_PRE_ROLLBACK,
release.Hook_POST_ROLLBACK,
},
},
}
upgradedRel.Manifest = "hello world"
rs.env.Releases.Update(rel)
rs.env.Releases.Create(upgradedRel)
req := &services.RollbackReleaseRequest{
Name: rel.Name,
}
res, err := rs.RollbackRelease(c, req)
if err != nil {
t.Fatalf("Failed rollback: %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)
}
if res.Release.Version != 3 {
t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version)
}
updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
if len(updated.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks))
}
if updated.Hooks[0].Manifest != manifestWithHook {
t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest)
}
anotherUpgradedRelease := upgradeReleaseVersion(upgradedRel)
rs.env.Releases.Update(upgradedRel)
rs.env.Releases.Create(anotherUpgradedRelease)
res, err = rs.RollbackRelease(c, req)
if err != nil {
t.Fatalf("Failed rollback: %s", err)
}
updated, err = rs.env.Releases.Get(res.Release.Name, res.Release.Version)
if err != nil {
t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases)
}
if len(updated.Hooks) != 1 {
t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks))
}
841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
if updated.Hooks[0].Manifest != manifestWithRollbackHooks {
t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest)
}
if res.Release.Version != 4 {
t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version)
}
if updated.Hooks[0].Events[0] != release.Hook_PRE_ROLLBACK {
t.Errorf("Expected event 0 to be pre rollback")
}
if updated.Hooks[0].Events[1] != release.Hook_POST_ROLLBACK {
t.Errorf("Expected event 1 to be post rollback")
}
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, "hello world") {
t.Errorf("unexpected output: %s", rel.Manifest)
}
}
func TestUninstallRelease(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rs.env.Releases.Create(releaseStub())
req := &services.UninstallReleaseRequest{
Name: "angry-panda",
}
res, err := rs.UninstallRelease(c, req)
if err != nil {
t.Fatalf("Failed uninstall: %s", err)
}
if res.Release.Name != "angry-panda" {
t.Errorf("Expected angry-panda, got %q", res.Release.Name)
}
if res.Release.Info.Status.Code != release.Status_DELETED {
t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code)
}
if res.Release.Hooks[0].LastRun.Seconds == 0 {
t.Error("Expected LastRun to be greater than zero.")
}
if res.Release.Info.Deleted.Seconds <= 0 {
t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds)
}
}
func TestUninstallPurgeRelease(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
rs.env.Releases.Create(rel)
upgradedRel := upgradeReleaseVersion(rel)
rs.env.Releases.Update(rel)
rs.env.Releases.Create(upgradedRel)
911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
req := &services.UninstallReleaseRequest{
Name: "angry-panda",
Purge: true,
}
res, err := rs.UninstallRelease(c, req)
if err != nil {
t.Fatalf("Failed uninstall: %s", err)
}
if res.Release.Name != "angry-panda" {
t.Errorf("Expected angry-panda, got %q", res.Release.Name)
}
if res.Release.Info.Status.Code != release.Status_DELETED {
t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code)
}
if res.Release.Info.Deleted.Seconds <= 0 {
t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds)
}
rels, err := rs.GetHistory(helm.NewContext(), &services.GetHistoryRequest{Name: "angry-panda"})
if err != nil {
t.Fatal(err)
}
if len(rels.Releases) != 0 {
t.Errorf("Expected no releases in storage, got %d", len(rels.Releases))
}
}
func TestUninstallPurgeDeleteRelease(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rs.env.Releases.Create(releaseStub())
req := &services.UninstallReleaseRequest{
Name: "angry-panda",
}
_, err := rs.UninstallRelease(c, req)
if err != nil {
t.Fatalf("Failed uninstall: %s", err)
}
req2 := &services.UninstallReleaseRequest{
Name: "angry-panda",
Purge: true,
}
_, err2 := rs.UninstallRelease(c, req2)
if err2 != nil && err2.Error() != "'angry-panda' has no deployed releases" {
t.Errorf("Failed uninstall: %s", err2)
}
}
func TestUninstallReleaseNoHooks(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rs.env.Releases.Create(releaseStub())
req := &services.UninstallReleaseRequest{
Name: "angry-panda",
DisableHooks: true,
}
res, err := rs.UninstallRelease(c, req)
if err != nil {
t.Errorf("Failed uninstall: %s", err)
}
981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
// The default value for a protobuf timestamp is nil.
if res.Release.Hooks[0].LastRun != nil {
t.Errorf("Expected LastRun to be zero, got %d.", res.Release.Hooks[0].LastRun.Seconds)
}
}
func TestGetReleaseContent(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
res, err := rs.GetReleaseContent(c, &services.GetReleaseContentRequest{Name: rel.Name, Version: 1})
if err != nil {
t.Errorf("Error getting release content: %s", err)
}
if res.Release.Chart.Metadata.Name != rel.Chart.Metadata.Name {
t.Errorf("Expected %q, got %q", rel.Chart.Metadata.Name, res.Release.Chart.Metadata.Name)
}
}
func TestGetReleaseStatus(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1})
if err != nil {
t.Errorf("Error getting release content: %s", err)
}
if res.Info.Status.Code != release.Status_DEPLOYED {
t.Errorf("Expected %d, got %d", release.Status_DEPLOYED, res.Info.Status.Code)
}
}
func TestGetReleaseStatusDeleted(t *testing.T) {
c := helm.NewContext()
rs := rsFixture()
rel := releaseStub()
rel.Info.Status.Code = release.Status_DELETED
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1})
if err != nil {
t.Fatalf("Error getting release content: %s", err)
}
if res.Info.Status.Code != release.Status_DELETED {
t.Errorf("Expected %d, got %d", release.Status_DELETED, res.Info.Status.Code)
}
}
func TestListReleases(t *testing.T) {
rs := rsFixture()
num := 7
for i := 0; i < num; i++ {
rel := releaseStub()
rel.Name = fmt.Sprintf("rel-%d", i)
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
1051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
}
mrs := &mockListServer{}
if err := rs.ListReleases(&services.ListReleasesRequest{Offset: "", Limit: 64}, mrs); err != nil {
t.Fatalf("Failed listing: %s", err)
}
if len(mrs.val.Releases) != num {
t.Errorf("Expected %d releases, got %d", num, len(mrs.val.Releases))
}
}
func TestListReleasesByStatus(t *testing.T) {
rs := rsFixture()
stubs := []*release.Release{
namedReleaseStub("kamal", release.Status_DEPLOYED),
namedReleaseStub("astrolabe", release.Status_DELETED),
namedReleaseStub("octant", release.Status_FAILED),
namedReleaseStub("sextant", release.Status_UNKNOWN),
}
for _, stub := range stubs {
if err := rs.env.Releases.Create(stub); err != nil {
t.Fatalf("Could not create stub: %s", err)
}
}
tests := []struct {
statusCodes []release.Status_Code
names []string
}{
{
names: []string{"kamal"},
statusCodes: []release.Status_Code{release.Status_DEPLOYED},
},
{
names: []string{"astrolabe"},
statusCodes: []release.Status_Code{release.Status_DELETED},
},
{
names: []string{"kamal", "octant"},
statusCodes: []release.Status_Code{release.Status_DEPLOYED, release.Status_FAILED},
},
{
names: []string{"kamal", "astrolabe", "octant", "sextant"},
statusCodes: []release.Status_Code{
release.Status_DEPLOYED,
release.Status_DELETED,
release.Status_FAILED,
release.Status_UNKNOWN,
},
},
}
for i, tt := range tests {
mrs := &mockListServer{}
if err := rs.ListReleases(&services.ListReleasesRequest{StatusCodes: tt.statusCodes, Offset: "", Limit: 64}, mrs); err != nil {
t.Fatalf("Failed listing %d: %s", i, err)
}
if len(tt.names) != len(mrs.val.Releases) {
t.Fatalf("Expected %d releases, got %d", len(tt.names), len(mrs.val.Releases))
}
for _, name := range tt.names {
found := false
for _, rel := range mrs.val.Releases {
if rel.Name == name {
found = true
}
}
1121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
if !found {
t.Errorf("%d: Did not find name %q", i, name)
}
}
}
}
func TestListReleasesSort(t *testing.T) {
rs := rsFixture()
// Put them in by reverse order so that the mock doesn't "accidentally"
// sort.
num := 7
for i := num; i > 0; i-- {
rel := releaseStub()
rel.Name = fmt.Sprintf("rel-%d", i)
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
}
limit := 6
mrs := &mockListServer{}
req := &services.ListReleasesRequest{
Offset: "",
Limit: int64(limit),
SortBy: services.ListSort_NAME,
}
if err := rs.ListReleases(req, mrs); err != nil {
t.Fatalf("Failed listing: %s", err)
}
if len(mrs.val.Releases) != limit {
t.Errorf("Expected %d releases, got %d", limit, len(mrs.val.Releases))
}
for i := 0; i < limit; i++ {
n := fmt.Sprintf("rel-%d", i+1)
if mrs.val.Releases[i].Name != n {
t.Errorf("Expected %q, got %q", n, mrs.val.Releases[i].Name)
}
}
}
func TestListReleasesFilter(t *testing.T) {
rs := rsFixture()
names := []string{
"axon",
"dendrite",
"neuron",
"neuroglia",
"synapse",
"nucleus",
"organelles",
}
num := 7
for i := 0; i < num; i++ {
rel := releaseStub()
rel.Name = names[i]
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
}
mrs := &mockListServer{}
req := &services.ListReleasesRequest{
Offset: "",
Limit: 64,
Filter: "neuro[a-z]+",
SortBy: services.ListSort_NAME,
11911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258
}
if err := rs.ListReleases(req, mrs); err != nil {
t.Fatalf("Failed listing: %s", err)
}
if len(mrs.val.Releases) != 2 {
t.Errorf("Expected 2 releases, got %d", len(mrs.val.Releases))
}
if mrs.val.Releases[0].Name != "neuroglia" {
t.Errorf("Unexpected sort order: %v.", mrs.val.Releases)
}
if mrs.val.Releases[1].Name != "neuron" {
t.Errorf("Unexpected sort order: %v.", mrs.val.Releases)
}
}
func mockEnvironment() *environment.Environment {
e := environment.New()
e.Releases = storage.Init(driver.NewMemory())
e.KubeClient = &environment.PrintingKubeClient{Out: os.Stdout}
return e
}
func newUpdateFailingKubeClient() *updateFailingKubeClient {
return &updateFailingKubeClient{
PrintingKubeClient: environment.PrintingKubeClient{Out: os.Stdout},
}
}
type updateFailingKubeClient struct {
environment.PrintingKubeClient
}
func (u *updateFailingKubeClient) Update(namespace string, originalReader, modifiedReader io.Reader) error {
return errors.New("Failed update in kube client")
}
func newHookFailingKubeClient() *hookFailingKubeClient {
return &hookFailingKubeClient{
PrintingKubeClient: environment.PrintingKubeClient{Out: os.Stdout},
}
}
type hookFailingKubeClient struct {
environment.PrintingKubeClient
}
func (h *hookFailingKubeClient) WatchUntilReady(ns string, r io.Reader) error {
return errors.New("Failed watch")
}
type mockListServer struct {
val *services.ListReleasesResponse
}
func (l *mockListServer) Send(res *services.ListReleasesResponse) error {
l.val = res
return nil
}
func (l *mockListServer) Context() context.Context { return helm.NewContext() }
func (l *mockListServer) SendMsg(v interface{}) error { return nil }
func (l *mockListServer) RecvMsg(v interface{}) error { return nil }
func (l *mockListServer) SendHeader(m metadata.MD) error { return nil }
func (l *mockListServer) SetTrailer(m metadata.MD) {}