hacking out a naive approach to stages

pull/2440/head
Kent Rancourt 9 years ago
parent 5ed1c42463
commit af1f7b6775

@ -50,4 +50,7 @@ message Release {
// Namespace is the kubernetes namespace of the release. // Namespace is the kubernetes namespace of the release.
string namespace = 8; string namespace = 8;
// Stages are subsets of rendered templates indexed by stage number
map<int32, string> stages = 9;
} }

@ -35,6 +35,8 @@ type Release struct {
Version int32 `protobuf:"varint,7,opt,name=version" json:"version,omitempty"` Version int32 `protobuf:"varint,7,opt,name=version" json:"version,omitempty"`
// Namespace is the kubernetes namespace of the release. // Namespace is the kubernetes namespace of the release.
Namespace string `protobuf:"bytes,8,opt,name=namespace" json:"namespace,omitempty"` Namespace string `protobuf:"bytes,8,opt,name=namespace" json:"namespace,omitempty"`
// Stages are subsets of rendered templates indexed by stage number
Stages map[int32]string `protobuf:"bytes,9,rep,name=stages" json:"stages,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
} }
func (m *Release) Reset() { *m = Release{} } func (m *Release) Reset() { *m = Release{} }
@ -98,6 +100,13 @@ func (m *Release) GetNamespace() string {
return "" return ""
} }
func (m *Release) GetStages() map[int32]string {
if m != nil {
return m.Stages
}
return nil
}
func init() { func init() {
proto.RegisterType((*Release)(nil), "hapi.release.Release") proto.RegisterType((*Release)(nil), "hapi.release.Release")
} }
@ -105,21 +114,25 @@ func init() {
func init() { proto.RegisterFile("hapi/release/release.proto", fileDescriptor2) } func init() { proto.RegisterFile("hapi/release/release.proto", fileDescriptor2) }
var fileDescriptor2 = []byte{ var fileDescriptor2 = []byte{
// 256 bytes of a gzipped FileDescriptorProto // 318 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xbf, 0x4e, 0xc3, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x91, 0x31, 0x4f, 0xfb, 0x30,
0x0c, 0xc6, 0x95, 0x36, 0x7f, 0x1a, 0xc3, 0x82, 0x07, 0xb0, 0x22, 0x86, 0x88, 0x01, 0x22, 0x86, 0x10, 0xc5, 0x95, 0xa6, 0x4e, 0x9a, 0xeb, 0x7f, 0xf8, 0x73, 0x42, 0x60, 0x45, 0x0c, 0x81, 0x01,
0x54, 0x82, 0x37, 0x80, 0x05, 0xd6, 0x1b, 0xd9, 0x8e, 0xe8, 0x42, 0x4e, 0xa5, 0xe7, 0x28, 0x17, 0x22, 0x86, 0x54, 0x82, 0x85, 0x32, 0x82, 0x90, 0x60, 0x35, 0x1b, 0x9b, 0xa9, 0x9c, 0x36, 0x6a,
0xf1, 0x2c, 0x3c, 0x2e, 0xba, 0x3f, 0x85, 0x94, 0x2e, 0x4e, 0xec, 0xdf, 0xa7, 0xcf, 0xdf, 0x19, 0x6b, 0x57, 0x71, 0xa8, 0xd4, 0x2f, 0xc1, 0x67, 0x46, 0x3e, 0xbb, 0x90, 0xc2, 0xe2, 0xf8, 0xee,
0xaa, 0x41, 0x8e, 0x7a, 0x3b, 0xa9, 0x4f, 0x25, 0xad, 0x3a, 0x7c, 0xdb, 0x71, 0xe2, 0x99, 0xf1, 0xfd, 0x72, 0xef, 0xf2, 0x02, 0xf9, 0x42, 0x6e, 0x9a, 0x49, 0xab, 0x56, 0x4a, 0x5a, 0xb5, 0x7f,
0xdc, 0xb1, 0x36, 0xce, 0xaa, 0xab, 0x23, 0xe5, 0xc0, 0xbc, 0x0b, 0xb2, 0x7f, 0x40, 0x9b, 0x9e, 0x56, 0x9b, 0xd6, 0x74, 0x06, 0xff, 0x39, 0xad, 0x0a, 0xbd, 0xfc, 0xf4, 0x80, 0x5c, 0x18, 0xb3,
0x8f, 0x40, 0x37, 0xc8, 0x69, 0xde, 0x76, 0x6c, 0x7a, 0xfd, 0x11, 0xc1, 0xe5, 0x12, 0xb8, 0x1a, 0xf4, 0xd8, 0x2f, 0xa1, 0xd1, 0xb5, 0x39, 0x10, 0x66, 0x0b, 0xd9, 0x76, 0x93, 0x99, 0xd1, 0x75,
0xe6, 0x37, 0xdf, 0x2b, 0x28, 0x44, 0xf0, 0x41, 0x84, 0xd4, 0xc8, 0xbd, 0xa2, 0xa4, 0x4e, 0x9a, 0x33, 0x0f, 0xc2, 0x49, 0x5f, 0x70, 0xa7, 0xef, 0x5f, 0x7c, 0xc6, 0x90, 0x0a, 0x3f, 0x07, 0x11,
0x52, 0xf8, 0x7f, 0xbc, 0x85, 0xd4, 0xd9, 0xd3, 0xaa, 0x4e, 0x9a, 0xb3, 0x07, 0x6c, 0x97, 0xf9, 0x86, 0x5a, 0xae, 0x15, 0x8f, 0x8a, 0xa8, 0xcc, 0x04, 0xdd, 0xf1, 0x12, 0x86, 0x6e, 0x3c, 0x1f,
0xda, 0x57, 0xd3, 0xb3, 0xf0, 0x1c, 0xef, 0x20, 0xf3, 0xb6, 0xb4, 0xf6, 0xc2, 0x8b, 0x20, 0x0c, 0x14, 0x51, 0x39, 0xbe, 0xc1, 0xaa, 0xbf, 0x5f, 0xf5, 0xa2, 0x6b, 0x23, 0x48, 0xc7, 0x2b, 0x60,
0x9b, 0x9e, 0x5d, 0x15, 0x81, 0xe3, 0x3d, 0xe4, 0x21, 0x18, 0xa5, 0x4b, 0xcb, 0xa8, 0xf4, 0x44, 0x34, 0x96, 0xc7, 0x04, 0x1e, 0x79, 0xd0, 0x3b, 0x3d, 0xba, 0x53, 0x78, 0x1d, 0xaf, 0x21, 0xf1,
0x44, 0x05, 0x56, 0xb0, 0xd9, 0x4b, 0xa3, 0x7b, 0x65, 0x67, 0xca, 0x7c, 0xa8, 0xdf, 0x1e, 0x1b, 0x8b, 0xf1, 0x61, 0x7f, 0x64, 0x20, 0x49, 0x11, 0x81, 0xc0, 0x1c, 0x46, 0x6b, 0xa9, 0x9b, 0x5a,
0xc8, 0xdc, 0x41, 0x2c, 0xe5, 0xf5, 0xfa, 0x34, 0xd9, 0x0b, 0xf3, 0x4e, 0x04, 0x01, 0x12, 0x14, 0xd9, 0x8e, 0x33, 0x5a, 0xea, 0xbb, 0xc6, 0x12, 0x98, 0x0b, 0xc4, 0xf2, 0xa4, 0x88, 0xff, 0x6e,
0x5f, 0x6a, 0xb2, 0x9a, 0x0d, 0x15, 0x75, 0xd2, 0x64, 0xe2, 0xd0, 0xe2, 0x35, 0x94, 0xee, 0x91, 0xf6, 0x6c, 0xcc, 0x52, 0x78, 0x00, 0x39, 0xa4, 0x5b, 0xd5, 0xda, 0xc6, 0x68, 0x9e, 0x16, 0x51,
0x76, 0x94, 0x9d, 0xa2, 0x8d, 0x5f, 0xf0, 0x37, 0x78, 0x2a, 0xdf, 0x8a, 0x68, 0xf7, 0x9e, 0xfb, 0xc9, 0xc4, 0xbe, 0xc4, 0x33, 0xc8, 0xdc, 0x47, 0xda, 0x8d, 0x9c, 0x29, 0x3e, 0x22, 0x83, 0x9f,
0x63, 0x3d, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x8f, 0xec, 0x97, 0xbb, 0x01, 0x00, 0x00, 0x06, 0x4e, 0x21, 0xb1, 0x9d, 0x9c, 0x2b, 0xcb, 0x33, 0xb2, 0x38, 0x3f, 0xb4, 0x08, 0xa9, 0x55,
0xaf, 0xc4, 0x3c, 0xe9, 0xae, 0xdd, 0x89, 0xf0, 0x42, 0x3e, 0x85, 0x71, 0xaf, 0x8d, 0xff, 0x21,
0x5e, 0xaa, 0x1d, 0xe5, 0xca, 0x84, 0xbb, 0xe2, 0x31, 0xb0, 0xad, 0x5c, 0x7d, 0x28, 0xca, 0x35,
0x13, 0xbe, 0xb8, 0x1f, 0xdc, 0x45, 0x0f, 0xd9, 0x5b, 0x1a, 0x1c, 0xde, 0x13, 0xfa, 0x45, 0xb7,
0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe4, 0x60, 0xb7, 0xf2, 0x31, 0x02, 0x00, 0x00,
} }

@ -666,7 +666,7 @@ func init() { proto.RegisterFile("hapi/rudder/rudder.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 584 bytes of a gzipped FileDescriptorProto // 584 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x56, 0xd1, 0x8e, 0xd2, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x56, 0xd1, 0x8e, 0xd2, 0x40,
0x14, 0xa5, 0xcb, 0x52, 0xe0, 0x92, 0x55, 0x32, 0xd9, 0x42, 0xd3, 0xf8, 0x40, 0xfa, 0x60, 0x88, 0x14, 0xa5, 0xcb, 0x52, 0xe0, 0x92, 0x55, 0x32, 0xd9, 0x42, 0xd3, 0xf8, 0x40, 0xfa, 0x60, 0x88,
0xeb, 0x96, 0x04, 0x7d, 0xf4, 0x45, 0x59, 0xdc, 0xdd, 0x18, 0xd9, 0x64, 0x2a, 0x6e, 0xe2, 0x5b, 0xeb, 0x96, 0x04, 0x7d, 0xf4, 0x45, 0x59, 0xdc, 0xdd, 0x18, 0xd9, 0x64, 0x2a, 0x6e, 0xe2, 0x5b,
0x17, 0x2e, 0x58, 0x2d, 0x6d, 0x9d, 0x4e, 0xf7, 0x51, 0xfd, 0x1a, 0xff, 0x43, 0xbf, 0xcc, 0xb4, 0x17, 0x2e, 0x58, 0x2d, 0x6d, 0x9d, 0x4e, 0xf7, 0x51, 0xfd, 0x1a, 0xff, 0x43, 0xbf, 0xcc, 0xb4,

@ -0,0 +1,20 @@
/*
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 stages
// StageAnno is the annotation name for a stage
const StageAnno = "helm.sh/stage"

@ -0,0 +1,21 @@
/*
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 tiller
import "bytes"
type docMap map[int]*bytes.Buffer

@ -29,6 +29,7 @@ import (
"k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
util "k8s.io/helm/pkg/releaseutil" util "k8s.io/helm/pkg/releaseutil"
"k8s.io/helm/pkg/stages"
) )
var events = map[string]release.Hook_Event{ var events = map[string]release.Hook_Event{
@ -72,9 +73,9 @@ type manifest struct {
// //
// Files that do not parse into the expected format are simply placed into a map and // Files that do not parse into the expected format are simply placed into a map and
// returned. // returned.
func sortManifests(files map[string]string, apis chartutil.VersionSet, sort SortOrder) ([]*release.Hook, []manifest, error) { func sortManifests(files map[string]string, apis chartutil.VersionSet, sort SortOrder) ([]*release.Hook, stageMap, error) {
hs := []*release.Hook{} hs := []*release.Hook{}
generic := []manifest{} sm := stageMap{}
for n, c := range files { for n, c := range files {
// Skip partials. We could return these as a separate map, but there doesn't // Skip partials. We could return these as a separate map, but there doesn't
@ -93,21 +94,30 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort
if err != nil { if err != nil {
e := fmt.Errorf("YAML parse error on %s: %s", n, err) e := fmt.Errorf("YAML parse error on %s: %s", n, err)
return hs, generic, e return hs, sm, e
} }
if sh.Version != "" && !apis.Has(sh.Version) { if sh.Version != "" && !apis.Has(sh.Version) {
return hs, generic, fmt.Errorf("apiVersion %q in %s is not available", sh.Version, n) return hs, sm, fmt.Errorf("apiVersion %q in %s is not available", sh.Version, n)
} }
if sh.Metadata == nil || sh.Metadata.Annotations == nil || len(sh.Metadata.Annotations) == 0 { if sh.Metadata == nil || sh.Metadata.Annotations == nil || len(sh.Metadata.Annotations) == 0 {
generic = append(generic, manifest{name: n, content: c, head: &sh}) sm.add(0, manifest{name: n, content: c, head: &sh})
continue continue
} }
hookTypes, ok := sh.Metadata.Annotations[hooks.HookAnno] hookTypes, ok := sh.Metadata.Annotations[hooks.HookAnno]
if !ok { if !ok {
generic = append(generic, manifest{name: n, content: c, head: &sh}) // We're not a hook. Might we belong to a stage?
stgNo := 0
stgNoStr, ok := sh.Metadata.Annotations[stages.StageAnno]
if ok {
stgNo, err = strconv.Atoi(stgNoStr)
if err != nil {
stgNo = 0
}
}
sm.add(stgNo, manifest{name: n, content: c, head: &sh})
continue continue
} }
@ -142,5 +152,5 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort
} }
hs = append(hs, h) hs = append(hs, h)
} }
return hs, sortByKind(generic, sort), nil return hs, sortByKind(sm, sort), nil
} }

@ -120,14 +120,14 @@ metadata:
manifests[o.path] = o.manifest manifests[o.path] = o.manifest
} }
hs, generic, err := sortManifests(manifests, chartutil.NewVersionSet("v1", "v1beta1"), InstallOrder) hs, stgs, err := sortManifests(manifests, chartutil.NewVersionSet("v1", "v1beta1"), InstallOrder)
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %s", err) t.Fatalf("Unexpected error: %s", err)
} }
// This test will fail if 'six' or 'seven' was added. // This test will fail if 'six' or 'seven' was added.
if len(generic) != 1 { if len(stgs[0]) != 1 {
t.Errorf("Expected 1 generic manifest, got %d", len(generic)) t.Errorf("Expected 1 generic manifest, got %d", len(stgs[0]))
} }
if len(hs) != 3 { if len(hs) != 3 {
@ -161,7 +161,7 @@ metadata:
} }
// Verify the sort order // Verify the sort order
sorted := make([]manifest, len(data)) sortedStg := make(stage, len(data))
for i, s := range data { for i, s := range data {
var sh util.SimpleHead var sh util.SimpleHead
err := yaml.Unmarshal([]byte(s.manifest), &sh) err := yaml.Unmarshal([]byte(s.manifest), &sh)
@ -169,16 +169,17 @@ metadata:
// This is expected for manifests that are corrupt or empty. // This is expected for manifests that are corrupt or empty.
t.Log(err) t.Log(err)
} }
sorted[i] = manifest{ sortedStg[i] = manifest{
content: s.manifest, content: s.manifest,
name: s.name, name: s.name,
head: &sh, head: &sh,
} }
} }
sorted = sortByKind(sorted, InstallOrder) sortedStgs := stageMap{0: sortedStg}
for i, m := range generic { sortedStg = sortByKind(sortedStgs, InstallOrder)[0]
if m.content != sorted[i].content { for i, m := range stgs[0] {
t.Errorf("Expected %q, got %q", m.content, sorted[i].content) if m.content != sortedStg[i].content {
t.Errorf("Expected %q, got %q", m.content, sortedStg[i].content)
} }
} }

@ -82,10 +82,14 @@ var UninstallOrder SortOrder = []string{
// sortByKind does an in-place sort of manifests by Kind. // sortByKind does an in-place sort of manifests by Kind.
// //
// Results are sorted by 'ordering' // Results are sorted by 'ordering'
func sortByKind(manifests []manifest, ordering SortOrder) []manifest { func sortByKind(sm stageMap, ordering SortOrder) stageMap {
ks := newKindSorter(manifests, ordering) newSM := stageMap{}
for stgNo, stg := range sm {
ks := newKindSorter(stg, ordering)
sort.Sort(ks) sort.Sort(ks)
return ks.manifests newSM[stgNo] = ks.manifests
}
return newSM
} }
type kindSorter struct { type kindSorter struct {

@ -24,7 +24,8 @@ import (
) )
func TestKindSorter(t *testing.T) { func TestKindSorter(t *testing.T) {
manifests := []manifest{ stgs := stageMap{
0: {
{ {
name: "i", name: "i",
content: "", content: "",
@ -140,6 +141,7 @@ func TestKindSorter(t *testing.T) {
content: "", content: "",
head: &util.SimpleHead{Kind: "StatefulSet"}, head: &util.SimpleHead{Kind: "StatefulSet"},
}, },
},
} }
for _, test := range []struct { for _, test := range []struct {
@ -152,11 +154,11 @@ func TestKindSorter(t *testing.T) {
} { } {
var buf bytes.Buffer var buf bytes.Buffer
t.Run(test.description, func(t *testing.T) { t.Run(test.description, func(t *testing.T) {
if got, want := len(test.expected), len(manifests); got != want { if got, want := len(test.expected), len(stgs[0]); got != want {
t.Fatalf("Expected %d names in order, got %d", want, got) t.Fatalf("Expected %d names in order, got %d", want, got)
} }
defer buf.Reset() defer buf.Reset()
for _, r := range sortByKind(manifests, test.order) { for _, r := range sortByKind(stgs, test.order)[0] {
buf.WriteString(r.name) buf.WriteString(r.name)
} }
if got := buf.String(); got != test.expected { if got := buf.String(); got != test.expected {

@ -154,7 +154,8 @@ func DeleteRelease(rel *release.Release, vs chartutil.VersionSet, kubeClient env
} }
errs = []error{} errs = []error{}
for _, file := range filesToDelete { for _, stg := range filesToDelete {
for _, file := range stg {
b := bytes.NewBufferString(strings.TrimSpace(file.content)) b := bytes.NewBufferString(strings.TrimSpace(file.content))
if b.Len() == 0 { if b.Len() == 0 {
continue continue
@ -168,5 +169,6 @@ func DeleteRelease(rel *release.Release, vs chartutil.VersionSet, kubeClient env
errs = append(errs, err) errs = append(errs, err)
} }
} }
}
return kept, errs return kept, errs
} }

@ -448,11 +448,16 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
return nil, nil, err return nil, nil, err
} }
hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) hooks, dm, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
docMap := make(map[int32]string, len(dm))
for i, d := range dm {
docMap[int32(i)] = d.String()
}
// Store an updated release. // Store an updated release.
updatedRelease := &release.Release{ updatedRelease := &release.Release{
Name: req.Name, Name: req.Name,
@ -466,14 +471,14 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele
Description: "Preparing upgrade", // This should be overwritten later. Description: "Preparing upgrade", // This should be overwritten later.
}, },
Version: revision, Version: revision,
Manifest: manifestDoc.String(), Stages: docMap,
Hooks: hooks, Hooks: hooks,
} }
if len(notesTxt) > 0 { if len(notesTxt) > 0 {
updatedRelease.Info.Status.Notes = notesTxt updatedRelease.Info.Status.Notes = notesTxt
} }
err = validateManifest(s.env.KubeClient, currentRelease.Namespace, manifestDoc.Bytes()) err = validateManifests(s.env.KubeClient, currentRelease.Namespace, dm)
return currentRelease, updatedRelease, err return currentRelease, updatedRelease, err
} }
@ -723,7 +728,11 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
return nil, err return nil, err
} }
hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) hooks, dm, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions)
docMap := make(map[int32]string, len(dm))
for i, d := range dm {
docMap[int32(i)] = d.String()
}
if err != nil { if err != nil {
// Return a release with partial data so that client can show debugging // Return a release with partial data so that client can show debugging
// information. // information.
@ -740,9 +749,7 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
}, },
Version: 0, Version: 0,
} }
if manifestDoc != nil { rel.Stages = docMap
rel.Manifest = manifestDoc.String()
}
return rel, err return rel, err
} }
@ -758,7 +765,7 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
Status: &release.Status{Code: release.Status_UNKNOWN}, Status: &release.Status{Code: release.Status_UNKNOWN},
Description: "Initial install underway", // Will be overwritten. Description: "Initial install underway", // Will be overwritten.
}, },
Manifest: manifestDoc.String(), Stages: docMap,
Hooks: hooks, Hooks: hooks,
Version: int32(revision), Version: int32(revision),
} }
@ -766,7 +773,7 @@ func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*re
rel.Info.Status.Notes = notesTxt rel.Info.Status.Notes = notesTxt
} }
err = validateManifest(s.env.KubeClient, req.Namespace, manifestDoc.Bytes()) err = validateManifests(s.env.KubeClient, req.Namespace, dm)
return rel, err return rel, err
} }
@ -789,7 +796,7 @@ func GetVersionSet(client discovery.ServerGroupsInterface) (chartutil.VersionSet
return chartutil.NewVersionSet(versions...), nil return chartutil.NewVersionSet(versions...), nil
} }
func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values, vs chartutil.VersionSet) ([]*release.Hook, *bytes.Buffer, string, error) { func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values, vs chartutil.VersionSet) ([]*release.Hook, docMap, string, error) {
// Guard to make sure Tiller is at the right version to handle this chart. // Guard to make sure Tiller is at the right version to handle this chart.
sver := version.GetVersion() sver := version.GetVersion()
if ch.Metadata.TillerVersion != "" && if ch.Metadata.TillerVersion != "" &&
@ -823,32 +830,23 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
// Sort hooks, manifests, and partials. Only hooks and manifests are returned, // Sort hooks, manifests, and partials. Only hooks and manifests are returned,
// as partials are not used after renderer.Render. Empty manifests are also // as partials are not used after renderer.Render. Empty manifests are also
// removed here. // removed here.
hooks, manifests, err := sortManifests(files, vs, InstallOrder) hooks, sm, err := sortManifests(files, vs, InstallOrder)
if err != nil {
// By catching parse errors here, we can prevent bogus releases from going // Aggregate all valid manifests into one big doc per stage.
// to Kubernetes. dm := make(docMap, len(sm))
// for stgNo, stg := range sm {
// We return the files as a big blob of data to help the user debug parser dm[stgNo] = bytes.NewBuffer(nil)
// errors. for _, m := range stg {
b := bytes.NewBuffer(nil) dm[stgNo].WriteString("\n---\n# Source: " + m.name + "\n")
for name, content := range files { dm[stgNo].WriteString(m.content)
if len(strings.TrimSpace(content)) == 0 {
continue
}
b.WriteString("\n---\n# Source: " + name + "\n")
b.WriteString(content)
} }
return nil, b, "", err
} }
// Aggregate all valid manifests into one big doc. if err != nil {
b := bytes.NewBuffer(nil) return nil, dm, "", err
for _, m := range manifests {
b.WriteString("\n---\n# Source: " + m.name + "\n")
b.WriteString(m.content)
} }
return hooks, b, notes, nil return hooks, dm, notes, nil
} }
func (s *ReleaseServer) recordRelease(r *release.Release, reuse bool) { func (s *ReleaseServer) recordRelease(r *release.Release, reuse bool) {
@ -1094,11 +1092,16 @@ func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
return res, nil return res, nil
} }
func validateManifest(c environment.KubeClient, ns string, manifest []byte) error { func validateManifests(c environment.KubeClient, ns string, dm docMap) error {
r := bytes.NewReader(manifest) for _, d := range dm {
r := bytes.NewReader(d.Bytes())
_, err := c.BuildUnstructured(ns, r) _, err := c.BuildUnstructured(ns, r)
if err != nil {
return err return err
} }
}
return nil
}
// RunReleaseTest runs pre-defined tests stored as hooks on a given release // RunReleaseTest runs pre-defined tests stored as hooks on a given release
func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream services.ReleaseService_RunReleaseTestServer) error { func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream services.ReleaseService_RunReleaseTestServer) error {

@ -313,16 +313,16 @@ func TestInstallRelease(t *testing.T) {
t.Errorf("Expected event 0 is pre-delete") t.Errorf("Expected event 0 is pre-delete")
} }
if len(res.Release.Manifest) == 0 { if len(res.Release.Stages[0]) == 0 {
t.Errorf("No manifest returned: %v", res.Release) t.Errorf("No manifest returned: %v", res.Release)
} }
if len(rel.Manifest) == 0 { if len(rel.Stages[0]) == 0 {
t.Errorf("Expected manifest in %v", res) t.Errorf("Expected manifest in %v", res)
} }
if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { if !strings.Contains(rel.Stages[0], "---\n# Source: hello/templates/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest) t.Errorf("unexpected output: %s", rel.Stages[0])
} }
if rel.Info.Description != "Install complete" { if rel.Info.Description != "Install complete" {
@ -382,16 +382,16 @@ func TestInstallRelease_WithNotes(t *testing.T) {
t.Errorf("Expected event 0 is pre-delete") t.Errorf("Expected event 0 is pre-delete")
} }
if len(res.Release.Manifest) == 0 { if len(res.Release.Stages[0]) == 0 {
t.Errorf("No manifest returned: %v", res.Release) t.Errorf("No manifest returned: %v", res.Release)
} }
if len(rel.Manifest) == 0 { if len(rel.Stages[0]) == 0 {
t.Errorf("Expected manifest in %v", res) t.Errorf("Expected manifest in %v", res)
} }
if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { if !strings.Contains(rel.Stages[0], "---\n# Source: hello/templates/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest) t.Errorf("unexpected output: %s", rel.Stages[0])
} }
if rel.Info.Description != "Install complete" { if rel.Info.Description != "Install complete" {
@ -452,16 +452,16 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) {
t.Errorf("Expected event 0 is pre-delete") t.Errorf("Expected event 0 is pre-delete")
} }
if len(res.Release.Manifest) == 0 { if len(res.Release.Stages[0]) == 0 {
t.Errorf("No manifest returned: %v", res.Release) t.Errorf("No manifest returned: %v", res.Release)
} }
if len(rel.Manifest) == 0 { if len(rel.Stages[0]) == 0 {
t.Errorf("Expected manifest in %v", res) t.Errorf("Expected manifest in %v", res)
} }
if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { if !strings.Contains(rel.Stages[0], "---\n# Source: hello/templates/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest) t.Errorf("unexpected output: %s", rel.Stages[0])
} }
if rel.Info.Description != "Install complete" { if rel.Info.Description != "Install complete" {
@ -585,24 +585,24 @@ func TestInstallRelease_DryRun(t *testing.T) {
t.Errorf("Expected release name.") t.Errorf("Expected release name.")
} }
if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { if !strings.Contains(res.Release.Stages[0], "---\n# Source: hello/templates/hello\nhello: world") {
t.Errorf("unexpected output: %s", res.Release.Manifest) t.Errorf("unexpected output: %s", res.Release.Stages[0])
} }
if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world") { if !strings.Contains(res.Release.Stages[0], "---\n# Source: hello/templates/goodbye\ngoodbye: world") {
t.Errorf("unexpected output: %s", res.Release.Manifest) t.Errorf("unexpected output: %s", res.Release.Stages[0])
} }
if !strings.Contains(res.Release.Manifest, "hello: Earth") { if !strings.Contains(res.Release.Stages[0], "hello: Earth") {
t.Errorf("Should contain partial content. %s", res.Release.Manifest) t.Errorf("Should contain partial content. %s", res.Release.Stages[0])
} }
if strings.Contains(res.Release.Manifest, "hello: {{ template \"_planet\" . }}") { if strings.Contains(res.Release.Stages[0], "hello: {{ template \"_planet\" . }}") {
t.Errorf("Should not contain partial templates itself. %s", res.Release.Manifest) t.Errorf("Should not contain partial templates itself. %s", res.Release.Stages[0])
} }
if strings.Contains(res.Release.Manifest, "empty") { if strings.Contains(res.Release.Stages[0], "empty") {
t.Errorf("Should not contain template data for an empty file. %s", res.Release.Manifest) t.Errorf("Should not contain template data for an empty file. %s", res.Release.Stages[0])
} }
if _, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version); err == nil { if _, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version); err == nil {
@ -744,7 +744,7 @@ func TestUpdateRelease(t *testing.T) {
t.Errorf("Expected event 0 to be pre upgrade") t.Errorf("Expected event 0 to be pre upgrade")
} }
if len(res.Release.Manifest) == 0 { if len(res.Release.Stages[0]) == 0 {
t.Errorf("No manifest returned: %v", res.Release) t.Errorf("No manifest returned: %v", res.Release)
} }
@ -754,12 +754,12 @@ func TestUpdateRelease(t *testing.T) {
t.Errorf("Expected release values %q, got %q", rel.Config.Raw, res.Release.Config.Raw) t.Errorf("Expected release values %q, got %q", rel.Config.Raw, res.Release.Config.Raw)
} }
if len(updated.Manifest) == 0 { if len(updated.Stages[0]) == 0 {
t.Errorf("Expected manifest in %v", res) t.Errorf("Expected manifest in %v", res)
} }
if !strings.Contains(updated.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { if !strings.Contains(updated.Stages[0], "---\n# Source: hello/templates/hello\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest) t.Errorf("unexpected output: %s", rel.Stages[0])
} }
if res.Release.Version != 2 { if res.Release.Version != 2 {
@ -1060,7 +1060,8 @@ func TestRollbackRelease(t *testing.T) {
}, },
} }
upgradedRel.Manifest = "hello world" upgradedRel.Stages = map[int32]string{}
upgradedRel.Stages[0] = "hello world"
rs.env.Releases.Update(rel) rs.env.Releases.Update(rel)
rs.env.Releases.Create(upgradedRel) rs.env.Releases.Create(upgradedRel)
@ -1135,16 +1136,16 @@ func TestRollbackRelease(t *testing.T) {
t.Errorf("Expected event 1 to be post rollback") t.Errorf("Expected event 1 to be post rollback")
} }
if len(res.Release.Manifest) == 0 { if len(res.Release.Stages[0]) == 0 {
t.Errorf("No manifest returned: %v", res.Release) t.Errorf("No manifest returned: %v", res.Release)
} }
if len(updated.Manifest) == 0 { if len(updated.Stages[0]) == 0 {
t.Errorf("Expected manifest in %v", res) t.Errorf("Expected manifest in %v", res)
} }
if !strings.Contains(updated.Manifest, "hello world") { if !strings.Contains(updated.Stages[0], "hello world") {
t.Errorf("unexpected output: %s", rel.Manifest) t.Errorf("unexpected output: %s", rel.Stages[0])
} }
if res.Release.Info.Description != "Rollback to 2" { if res.Release.Info.Description != "Rollback to 2" {

@ -29,11 +29,13 @@ const resourcePolicyAnno = "helm.sh/resource-policy"
// during an uninstallRelease action. // during an uninstallRelease action.
const keepPolicy = "keep" const keepPolicy = "keep"
func filterManifestsToKeep(manifests []manifest) ([]manifest, []manifest) { func filterManifestsToKeep(sm stageMap) (stageMap, stageMap) {
remaining := []manifest{} remainingMap := stageMap{}
keep := []manifest{} keepMap := stageMap{}
for stgNo, stg := range sm {
for _, m := range manifests { remaining := stage{}
keep := stage{}
for _, m := range stg {
if m.head.Metadata == nil || m.head.Metadata.Annotations == nil || len(m.head.Metadata.Annotations) == 0 { if m.head.Metadata == nil || m.head.Metadata.Annotations == nil || len(m.head.Metadata.Annotations) == 0 {
remaining = append(remaining, m) remaining = append(remaining, m)
@ -52,14 +54,23 @@ func filterManifestsToKeep(manifests []manifest) ([]manifest, []manifest) {
} }
} }
return keep, remaining if len(remaining) > 0 {
remainingMap[stgNo] = remaining
}
if len(keep) > 0 {
keepMap[stgNo] = keep
}
}
return keepMap, remainingMap
} }
func summarizeKeptManifests(manifests []manifest) string { func summarizeKeptManifests(sm stageMap) string {
message := "These resources were kept due to the resource policy:\n" message := "These resources were kept due to the resource policy:\n"
for _, m := range manifests { for _, stg := range sm {
for _, m := range stg {
details := "[" + m.head.Kind + "] " + m.head.Metadata.Name + "\n" details := "[" + m.head.Kind + "] " + m.head.Metadata.Name + "\n"
message = message + details message = message + details
} }
}
return message return message
} }

@ -0,0 +1,29 @@
/*
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 tiller
type stage []manifest
type stageMap map[int]stage
func (s stageMap) add(stgNo int, man manifest) {
_, ok := s[stgNo]
if !ok {
s[stgNo] = stage{}
}
s[stgNo] = append(s[stgNo], man)
}
Loading…
Cancel
Save