Add chart/manifest weight based ordering

Signed-off-by: Zsolt Zadory <zsolt.zadory@nokia.com>
pull/5214/head
Zsolt Zadory 7 years ago
parent dd4afa6af0
commit 6127bbeb49

@ -89,5 +89,8 @@ message Metadata {
map<string,string> annotations = 16; map<string,string> annotations = 16;
// KubeVersion is a SemVer constraint specifying the version of Kubernetes required. // KubeVersion is a SemVer constraint specifying the version of Kubernetes required.
string kubeVersion = 17; string kubeVersion = 17;
// Deployment weight of this chart
uint32 weight = 18;
} }

@ -131,14 +131,16 @@ func (r *ReleaseModuleServiceServer) RollbackRelease(ctx context.Context, in *ru
grpclog.Print("rollback") grpclog.Print("rollback")
c := bytes.NewBufferString(in.Current.Manifest) c := bytes.NewBufferString(in.Current.Manifest)
t := bytes.NewBufferString(in.Target.Manifest) t := bytes.NewBufferString(in.Target.Manifest)
err := kubeClient.UpdateWithOptions(in.Target.Namespace, c, t, kube.UpdateOptions{ if err := kubeClient.UpdateWithOptions(in.Target.Namespace, c, t, kube.UpdateOptions{
Force: in.Force, Force: in.Force,
Recreate: in.Recreate, Recreate: in.Recreate,
Timeout: in.Timeout, Timeout: in.Timeout,
ShouldWait: in.Wait, ShouldWait: in.Wait,
CleanupOnFail: in.CleanupOnFail, CleanupOnFail: in.CleanupOnFail,
}) }); err != nil {
return &rudderAPI.RollbackReleaseResponse{}, err return &rudderAPI.RollbackReleaseResponse{}, err
}
return &rudderAPI.RollbackReleaseResponse{}, nil
} }
// UpgradeRelease upgrades manifests using kubernetes client // UpgradeRelease upgrades manifests using kubernetes client
@ -146,15 +148,17 @@ func (r *ReleaseModuleServiceServer) UpgradeRelease(ctx context.Context, in *rud
grpclog.Print("upgrade") grpclog.Print("upgrade")
c := bytes.NewBufferString(in.Current.Manifest) c := bytes.NewBufferString(in.Current.Manifest)
t := bytes.NewBufferString(in.Target.Manifest) t := bytes.NewBufferString(in.Target.Manifest)
err := kubeClient.UpdateWithOptions(in.Target.Namespace, c, t, kube.UpdateOptions{ if err := kubeClient.UpdateWithOptions(in.Target.Namespace, c, t, kube.UpdateOptions{
Force: in.Force, Force: in.Force,
Recreate: in.Recreate, Recreate: in.Recreate,
Timeout: in.Timeout, Timeout: in.Timeout,
ShouldWait: in.Wait, ShouldWait: in.Wait,
CleanupOnFail: in.CleanupOnFail, CleanupOnFail: in.CleanupOnFail,
}) }); err != nil {
// upgrade response object should be changed to include status // upgrade response object should be changed to include status
return &rudderAPI.UpgradeReleaseResponse{}, err return &rudderAPI.UpgradeReleaseResponse{}, err
}
return &rudderAPI.UpgradeReleaseResponse{}, nil
} }
// ReleaseStatus retrieves release status // ReleaseStatus retrieves release status

@ -0,0 +1,23 @@
# Chart and template weights
This part of the Best Practices Guide discusses how to manipulate the creation order of kubernetes resources
The weight of an object can be given at two levels:
- Chart: It can be defined in `Chart.yaml`. All templates directly defined in this chart is affected. It takes precedence over template weight.
- Template: A `helm.sh/order-weight` annotation shall be declared in the uppermost metadata section of a kubernetes object.
## Precedence
Every object will get a tuple value of (chart weight, object weight). Default is 0 for both.
Chart weight takes precedence over individual objects. Higher weight means it will be handled earlier.
## Scope
Weights have a global scope, so if subchart has higher weight then it will be handled sooner regardless of the inclusion order.
## How it works
Without the `--wait` flag it works like kind ordering - all objects will be sent to apiserver in one big manifest and objects are sorted accordingly (sortByWeight(sortByKind(templates))).
With the `--wait` flag equally weighted objects are grouped together, and these groups are then installed one-by-one. Helm waits for each group's resources individually for the given `--timeout` period.

@ -60,6 +60,7 @@ icon: A URL to an SVG or PNG image to be used as an icon (optional).
appVersion: The version of the app that this contains (optional). This needn't be SemVer. appVersion: The version of the app that this contains (optional). This needn't be SemVer.
deprecated: Whether this chart is deprecated (optional, boolean) deprecated: Whether this chart is deprecated (optional, boolean)
tillerVersion: The version of Tiller that this chart requires. This should be expressed as a SemVer range: ">2.0.0" (optional) tillerVersion: The version of Tiller that this chart requires. This should be expressed as a SemVer range: ">2.0.0" (optional)
weight: A number (>0) that represents the precedence of objects in the chart. Heavier objects will be installed sooner. (optional)
``` ```
If you are familiar with the `Chart.yaml` file format for Helm Classic, you will If you are familiar with the `Chart.yaml` file format for Helm Classic, you will

@ -68,6 +68,8 @@ type Dependency struct {
ImportValues []interface{} `json:"import-values,omitempty"` ImportValues []interface{} `json:"import-values,omitempty"`
// Alias usable alias to be used for the chart // Alias usable alias to be used for the chart
Alias string `json:"alias,omitempty"` Alias string `json:"alias,omitempty"`
// Deployment weight of this chart
Weight uint32 `json:"weight,omitempty"`
} }
// ErrNoRequirementsFile to detect error condition // ErrNoRequirementsFile to detect error condition

@ -423,6 +423,28 @@ func (c *Client) UpdateWithOptions(namespace string, originalReader, targetReade
return fmt.Errorf(strings.Join(append(updateErrors, cleanupErrors...), " && ")) return fmt.Errorf(strings.Join(append(updateErrors, cleanupErrors...), " && "))
} }
if opts.ShouldWait {
return c.waitForResources(time.Duration(opts.Timeout)*time.Second, target)
}
return nil
}
// RemoveDiff deletes resources from the current configuration that are
// not present in the target configuration
//
// Namespace will set the namespace.
func (c *Client) RemoveDiff(namespace string, originalReader, targetReader io.Reader) error {
original, err := c.BuildUnstructured(namespace, originalReader)
if err != nil {
return fmt.Errorf("failed decoding reader into objects: %s", err)
}
c.Log("building resources from updated manifest")
target, err := c.BuildUnstructured(namespace, targetReader)
if err != nil {
return fmt.Errorf("failed decoding reader into objects: %s", err)
}
for _, info := range original.Difference(target) { for _, info := range original.Difference(target) {
c.Log("Deleting %q in %s...", info.Name, info.Namespace) c.Log("Deleting %q in %s...", info.Name, info.Namespace)
@ -443,17 +465,6 @@ func (c *Client) UpdateWithOptions(namespace string, originalReader, targetReade
c.Log("Failed to delete %q, err: %s", info.Name, err) c.Log("Failed to delete %q, err: %s", info.Name, err)
} }
} }
if opts.ShouldWait {
err := c.waitForResources(time.Duration(opts.Timeout)*time.Second, target)
if opts.CleanupOnFail && err != nil {
c.Log("Cleanup on fail enabled: cleaning up newly created resources due to wait failure during update")
cleanupErrors = c.cleanup(newlyCreatedResources)
return fmt.Errorf(strings.Join(append([]string{err.Error()}, cleanupErrors...), " && "))
}
return err
}
return nil return nil
} }

@ -203,6 +203,10 @@ func TestUpdate(t *testing.T) {
if err := c.Update(v1.NamespaceDefault, objBody(&listA), objBody(&listB), false, false, 0, false); err != nil { if err := c.Update(v1.NamespaceDefault, objBody(&listA), objBody(&listB), false, false, 0, false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := c.RemoveDiff(v1.NamespaceDefault, objBody(&listA), objBody(&listB)); err != nil {
t.Fatal(err)
}
// TODO: Find a way to test methods that use Client Set // TODO: Find a way to test methods that use Client Set
// Test with a wait // Test with a wait
// if err := c.Update("test", objBody(&listB), objBody(&listC), false, 300, true); err != nil { // if err := c.Update("test", objBody(&listB), objBody(&listC), false, 300, true); err != nil {

@ -0,0 +1,62 @@
/*
Copyright The Helm Authors.
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 manifest
// Equalto implements = operator for weights
//
// Weights are equal if both Chart and Manifest attributes are equal
func (mw *Weight) Equalto(other *Weight) bool {
if mw == nil || other == nil {
return false
}
if mw.Chart == other.Chart && mw.Manifest == other.Manifest {
return true
}
return false
}
// LessThan implements < operator for weights
//
// Precedence is Weight.Chart > Weight.Manifest
func (mw *Weight) LessThan(other *Weight) bool {
if mw == nil || other == nil {
return false
}
if mw.Chart == other.Chart {
return mw.Manifest < other.Manifest
}
return mw.Chart < other.Chart
}
// GreaterThan implements > operator for weights
//
// Precedence is Weight.Chart > Weight.Manifest
func (mw *Weight) GreaterThan(other *Weight) bool {
if mw == nil || other == nil {
return false
}
if mw.Chart == other.Chart {
return mw.Manifest > other.Manifest
}
return mw.Chart > other.Chart
}

@ -19,8 +19,6 @@ package manifest
import ( import (
"regexp" "regexp"
"strings" "strings"
"k8s.io/helm/pkg/releaseutil"
) )
var ( var (
@ -38,7 +36,7 @@ func SplitManifests(templates map[string]string) []Manifest {
if len(match) == 2 { if len(match) == 2 {
h = strings.TrimSpace(match[1]) h = strings.TrimSpace(match[1])
} }
m := Manifest{Name: k, Content: v, Head: &releaseutil.SimpleHead{Kind: h}} m := Manifest{Name: k, Content: v, Head: &SimpleHead{Kind: h}}
listManifests = append(listManifests, m) listManifests = append(listManifests, m)
} }

@ -16,13 +16,29 @@ limitations under the License.
package manifest package manifest
import ( // ManifestOrderWeight is the label name for a manifest
"k8s.io/helm/pkg/releaseutil" const ManifestOrderWeight = "helm.sh/order-weight"
)
// Weight represents the deployment order of a manifest
type Weight struct {
Chart uint32
Manifest uint32
}
// SimpleHead defines what the structure of the head of a manifest file
type SimpleHead struct {
Version string `json:"apiVersion"`
Kind string `json:"kind,omitempty"`
Metadata *struct {
Name string `json:"name"`
Annotations map[string]string `json:"annotations"`
} `json:"metadata,omitempty"`
}
// Manifest represents a manifest file, which has a name and some content. // Manifest represents a manifest file, which has a name and some content.
type Manifest struct { type Manifest struct {
Name string Name string
Content string Content string
Head *releaseutil.SimpleHead Head *SimpleHead
Weight *Weight
} }

@ -38,7 +38,7 @@ func (x Metadata_Engine) String() string {
return proto.EnumName(Metadata_Engine_name, int32(x)) return proto.EnumName(Metadata_Engine_name, int32(x))
} }
func (Metadata_Engine) EnumDescriptor() ([]byte, []int) { func (Metadata_Engine) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_metadata_d6c714c73a051dcb, []int{1, 0} return fileDescriptor_metadata_a633fda7c87700ff, []int{1, 0}
} }
// Maintainer describes a Chart maintainer. // Maintainer describes a Chart maintainer.
@ -58,7 +58,7 @@ func (m *Maintainer) Reset() { *m = Maintainer{} }
func (m *Maintainer) String() string { return proto.CompactTextString(m) } func (m *Maintainer) String() string { return proto.CompactTextString(m) }
func (*Maintainer) ProtoMessage() {} func (*Maintainer) ProtoMessage() {}
func (*Maintainer) Descriptor() ([]byte, []int) { func (*Maintainer) Descriptor() ([]byte, []int) {
return fileDescriptor_metadata_d6c714c73a051dcb, []int{0} return fileDescriptor_metadata_a633fda7c87700ff, []int{0}
} }
func (m *Maintainer) XXX_Unmarshal(b []byte) error { func (m *Maintainer) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Maintainer.Unmarshal(m, b) return xxx_messageInfo_Maintainer.Unmarshal(m, b)
@ -138,7 +138,9 @@ type Metadata struct {
// made available for inspection by other applications. // made available for inspection by other applications.
Annotations map[string]string `protobuf:"bytes,16,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Annotations map[string]string `protobuf:"bytes,16,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// KubeVersion is a SemVer constraint specifying the version of Kubernetes required. // KubeVersion is a SemVer constraint specifying the version of Kubernetes required.
KubeVersion string `protobuf:"bytes,17,opt,name=kubeVersion,proto3" json:"kubeVersion,omitempty"` KubeVersion string `protobuf:"bytes,17,opt,name=kubeVersion,proto3" json:"kubeVersion,omitempty"`
// Deployment weight of this chart
Weight uint32 `protobuf:"varint,18,opt,name=weight,proto3" json:"weight,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -148,7 +150,7 @@ func (m *Metadata) Reset() { *m = Metadata{} }
func (m *Metadata) String() string { return proto.CompactTextString(m) } func (m *Metadata) String() string { return proto.CompactTextString(m) }
func (*Metadata) ProtoMessage() {} func (*Metadata) ProtoMessage() {}
func (*Metadata) Descriptor() ([]byte, []int) { func (*Metadata) Descriptor() ([]byte, []int) {
return fileDescriptor_metadata_d6c714c73a051dcb, []int{1} return fileDescriptor_metadata_a633fda7c87700ff, []int{1}
} }
func (m *Metadata) XXX_Unmarshal(b []byte) error { func (m *Metadata) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Metadata.Unmarshal(m, b) return xxx_messageInfo_Metadata.Unmarshal(m, b)
@ -287,6 +289,13 @@ func (m *Metadata) GetKubeVersion() string {
return "" return ""
} }
func (m *Metadata) GetWeight() uint32 {
if m != nil {
return m.Weight
}
return 0
}
func init() { func init() {
proto.RegisterType((*Maintainer)(nil), "hapi.chart.Maintainer") proto.RegisterType((*Maintainer)(nil), "hapi.chart.Maintainer")
proto.RegisterType((*Metadata)(nil), "hapi.chart.Metadata") proto.RegisterType((*Metadata)(nil), "hapi.chart.Metadata")
@ -294,36 +303,37 @@ func init() {
proto.RegisterEnum("hapi.chart.Metadata_Engine", Metadata_Engine_name, Metadata_Engine_value) proto.RegisterEnum("hapi.chart.Metadata_Engine", Metadata_Engine_name, Metadata_Engine_value)
} }
func init() { proto.RegisterFile("hapi/chart/metadata.proto", fileDescriptor_metadata_d6c714c73a051dcb) } func init() { proto.RegisterFile("hapi/chart/metadata.proto", fileDescriptor_metadata_a633fda7c87700ff) }
var fileDescriptor_metadata_d6c714c73a051dcb = []byte{ var fileDescriptor_metadata_a633fda7c87700ff = []byte{
// 435 bytes of a gzipped FileDescriptorProto // 449 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0x5d, 0x6b, 0xd4, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0x5d, 0x6b, 0xd4, 0x40,
0x14, 0x35, 0xcd, 0x66, 0x77, 0x73, 0x63, 0x35, 0x0e, 0x52, 0xc6, 0x22, 0x12, 0x16, 0x85, 0x7d, 0x14, 0x35, 0x4d, 0xb3, 0xbb, 0xb9, 0x71, 0x35, 0x5e, 0xa4, 0x8c, 0x45, 0x24, 0x2c, 0x0a, 0x79,
0xda, 0x82, 0xbe, 0x14, 0x1f, 0x04, 0x85, 0x52, 0x41, 0xbb, 0x95, 0xe0, 0x07, 0xf8, 0x36, 0x4d, 0xda, 0x82, 0xbe, 0x14, 0x1f, 0x04, 0x85, 0x52, 0x41, 0xbb, 0x95, 0xe0, 0x07, 0xf8, 0x36, 0x4d,
0x2e, 0xdd, 0x61, 0x93, 0x99, 0x30, 0x99, 0xad, 0xec, 0xaf, 0xf0, 0x2f, 0xcb, 0xdc, 0x64, 0x9a, 0x2e, 0xbb, 0xc3, 0x26, 0x33, 0x61, 0x32, 0xdb, 0xb2, 0xbf, 0xc8, 0xbf, 0x29, 0x33, 0xc9, 0x74,
0xac, 0xf4, 0xed, 0x9e, 0x73, 0x66, 0xce, 0xcc, 0xbd, 0xf7, 0xc0, 0x8b, 0x8d, 0x68, 0xe4, 0x59, 0x53, 0xf1, 0xed, 0x9e, 0x73, 0x66, 0xce, 0xe4, 0xdc, 0x7b, 0x03, 0x2f, 0x36, 0xbc, 0x15, 0x67,
0xb1, 0x11, 0xc6, 0x9e, 0xd5, 0x68, 0x45, 0x29, 0xac, 0x58, 0x35, 0x46, 0x5b, 0xcd, 0xc0, 0x49, 0xe5, 0x86, 0x6b, 0x73, 0xd6, 0x90, 0xe1, 0x15, 0x37, 0x7c, 0xd9, 0x6a, 0x65, 0x14, 0x82, 0x95,
0x2b, 0x92, 0x16, 0x9f, 0x01, 0xae, 0x84, 0x54, 0x56, 0x48, 0x85, 0x86, 0x31, 0x98, 0x28, 0x51, 0x96, 0x4e, 0x5a, 0x7c, 0x06, 0xb8, 0xe2, 0x42, 0x1a, 0x2e, 0x24, 0x69, 0x44, 0x38, 0x96, 0xbc,
0x23, 0x0f, 0xb2, 0x60, 0x19, 0xe7, 0x54, 0xb3, 0xe7, 0x10, 0x61, 0x2d, 0x64, 0xc5, 0x8f, 0x88, 0x21, 0x16, 0x64, 0x41, 0x1e, 0x17, 0xae, 0xc6, 0xe7, 0x10, 0x51, 0xc3, 0x45, 0xcd, 0x8e, 0x1c,
0xec, 0x00, 0x4b, 0x21, 0xdc, 0x99, 0x8a, 0x87, 0xc4, 0xb9, 0x72, 0xf1, 0x37, 0x82, 0xf9, 0x55, 0xd9, 0x03, 0x4c, 0x21, 0xdc, 0xe9, 0x9a, 0x85, 0x8e, 0xb3, 0xe5, 0xe2, 0x4f, 0x04, 0xb3, 0xab,
0xff, 0xd0, 0x83, 0x46, 0x0c, 0x26, 0x1b, 0x5d, 0x63, 0xef, 0x43, 0x35, 0xe3, 0x30, 0x6b, 0xf5, 0xe1, 0xa1, 0xff, 0x1a, 0x21, 0x1c, 0x6f, 0x54, 0x43, 0x83, 0x8f, 0xab, 0x91, 0xc1, 0xb4, 0x53,
0xce, 0x14, 0xd8, 0xf2, 0x30, 0x0b, 0x97, 0x71, 0xee, 0xa1, 0x53, 0xee, 0xd0, 0xb4, 0x52, 0x2b, 0x3b, 0x5d, 0x52, 0xc7, 0xc2, 0x2c, 0xcc, 0xe3, 0xc2, 0x43, 0xab, 0xdc, 0x92, 0xee, 0x84, 0x92,
0x3e, 0xa1, 0x0b, 0x1e, 0xb2, 0x0c, 0x92, 0x12, 0xdb, 0xc2, 0xc8, 0xc6, 0x3a, 0x35, 0x22, 0x75, 0xec, 0xd8, 0x5d, 0xf0, 0x10, 0x33, 0x48, 0x2a, 0xea, 0x4a, 0x2d, 0x5a, 0x63, 0xd5, 0xc8, 0xa9,
0x4c, 0xb1, 0x53, 0x98, 0x6f, 0x71, 0xff, 0x47, 0x9b, 0xb2, 0xe5, 0x53, 0xb2, 0xbd, 0xc7, 0xec, 0x63, 0x0a, 0x4f, 0x61, 0xb6, 0xa5, 0xfd, 0x9d, 0xd2, 0x55, 0xc7, 0x26, 0xce, 0xf6, 0x1e, 0xe3,
0x1c, 0x92, 0xfa, 0xbe, 0xe1, 0x96, 0xcf, 0xb2, 0x70, 0x99, 0xbc, 0x3d, 0x59, 0x0d, 0x23, 0x59, 0x39, 0x24, 0xcd, 0x7d, 0xe0, 0x8e, 0x4d, 0xb3, 0x30, 0x4f, 0xde, 0x9e, 0x2c, 0x0f, 0x2d, 0x59,
0x0d, 0xf3, 0xc8, 0xc7, 0x47, 0xd9, 0x09, 0x4c, 0x51, 0xdd, 0x4a, 0x85, 0x7c, 0x4e, 0x4f, 0xf6, 0x1e, 0xfa, 0x51, 0x8c, 0x8f, 0xe2, 0x09, 0x4c, 0x48, 0xae, 0x85, 0x24, 0x36, 0x73, 0x4f, 0x0e,
0xc8, 0xf5, 0x25, 0x0b, 0xad, 0x78, 0xdc, 0xf5, 0xe5, 0x6a, 0xf6, 0x0a, 0x40, 0x34, 0xf2, 0x67, 0xc8, 0xe6, 0x12, 0xa5, 0x92, 0x2c, 0xee, 0x73, 0xd9, 0x1a, 0x5f, 0x01, 0xf0, 0x56, 0xfc, 0x1c,
0xdf, 0x00, 0x90, 0x32, 0x62, 0xd8, 0x4b, 0x88, 0x0b, 0xad, 0x4a, 0x49, 0x1d, 0x24, 0x24, 0x0f, 0x02, 0x80, 0x53, 0x46, 0x0c, 0xbe, 0x84, 0xb8, 0x54, 0xb2, 0x12, 0x2e, 0x41, 0xe2, 0xe4, 0x03,
0x84, 0x73, 0xb4, 0xe2, 0xb6, 0xe5, 0x8f, 0x3b, 0x47, 0x57, 0x77, 0x8e, 0x8d, 0x77, 0x3c, 0xf6, 0x61, 0x1d, 0x0d, 0x5f, 0x77, 0xec, 0x71, 0xef, 0x68, 0xeb, 0xde, 0xb1, 0xf5, 0x8e, 0x73, 0xef,
0x8e, 0x9e, 0x71, 0x7a, 0x89, 0x8d, 0xc1, 0x42, 0x58, 0x2c, 0xf9, 0x93, 0x2c, 0x58, 0xce, 0xf3, 0xe8, 0x19, 0xab, 0x57, 0xd4, 0x6a, 0x2a, 0xb9, 0xa1, 0x8a, 0x3d, 0xc9, 0x82, 0x7c, 0x56, 0x8c,
0x11, 0xc3, 0x5e, 0xc3, 0xb1, 0x95, 0x55, 0x85, 0xc6, 0x5b, 0x3c, 0x25, 0x8b, 0x43, 0x92, 0x5d, 0x18, 0x7c, 0x0d, 0x73, 0x23, 0xea, 0x9a, 0xb4, 0xb7, 0x78, 0xea, 0x2c, 0x1e, 0x92, 0x78, 0x09,
0x42, 0x22, 0x94, 0xd2, 0x56, 0xb8, 0x7f, 0xb4, 0x3c, 0xa5, 0xe9, 0xbc, 0x39, 0x98, 0x8e, 0xcf, 0x09, 0x97, 0x52, 0x19, 0x6e, 0xbf, 0xa3, 0x63, 0xa9, 0xeb, 0xce, 0x9b, 0x07, 0xdd, 0xf1, 0xbb,
0xd2, 0xc7, 0xe1, 0xdc, 0x85, 0xb2, 0x66, 0x9f, 0x8f, 0x6f, 0xba, 0x25, 0x6d, 0x77, 0x37, 0xe8, 0xf4, 0xf1, 0x70, 0xee, 0x42, 0x1a, 0xbd, 0x2f, 0xc6, 0x37, 0xed, 0x90, 0xb6, 0xbb, 0x1b, 0xf2,
0x1f, 0x7b, 0xd6, 0x2d, 0x69, 0x44, 0x9d, 0x7e, 0x80, 0xf4, 0x7f, 0x0b, 0x97, 0xaa, 0x2d, 0xee, 0x8f, 0x3d, 0xeb, 0x87, 0x34, 0xa2, 0x6c, 0x3b, 0xef, 0x48, 0xac, 0x37, 0x86, 0x61, 0x16, 0xe4,
0xfb, 0xd4, 0xb8, 0xd2, 0xa5, 0xef, 0x4e, 0x54, 0x3b, 0x9f, 0x9a, 0x0e, 0xbc, 0x3f, 0x3a, 0x0f, 0xf3, 0x62, 0x40, 0xa7, 0x1f, 0x20, 0xfd, 0xd7, 0xda, 0x6e, 0xdb, 0x96, 0xf6, 0xc3, 0x36, 0xd9,
0x16, 0x19, 0x4c, 0x2f, 0xba, 0x05, 0x24, 0x30, 0xfb, 0xb1, 0xfe, 0xb2, 0xbe, 0xfe, 0xb5, 0x4e, 0xd2, 0x6e, 0xe5, 0x2d, 0xaf, 0x77, 0x7e, 0x9b, 0x7a, 0xf0, 0xfe, 0xe8, 0x3c, 0x58, 0x64, 0x30,
0x1f, 0xb1, 0x18, 0xa2, 0xcb, 0xeb, 0xef, 0xdf, 0xbe, 0xa6, 0xc1, 0xa7, 0xd9, 0xef, 0x88, 0xfe, 0xb9, 0xe8, 0x07, 0x93, 0xc0, 0xf4, 0xc7, 0xea, 0xcb, 0xea, 0xfa, 0xd7, 0x2a, 0x7d, 0x84, 0x31,
0x7c, 0x33, 0xa5, 0xdc, 0xbf, 0xfb, 0x17, 0x00, 0x00, 0xff, 0xff, 0x36, 0xf9, 0x0d, 0xa6, 0x14, 0x44, 0x97, 0xd7, 0xdf, 0xbf, 0x7d, 0x4d, 0x83, 0x4f, 0xd3, 0xdf, 0x91, 0xcb, 0x72, 0x33, 0x71,
0x03, 0x00, 0x00, 0xff, 0xc3, 0xbb, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x15, 0xc1, 0x9c, 0xdf, 0x2c, 0x03, 0x00,
0x00,
} }

@ -27,6 +27,7 @@ import (
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/manifest"
"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/timeconv" "k8s.io/helm/pkg/timeconv"
@ -206,7 +207,7 @@ func extractTestManifestsFromHooks(h []*release.Hook) ([]string, error) {
} }
func newTest(testManifest string) (*test, error) { func newTest(testManifest string) (*test, error) {
var sh util.SimpleHead var sh manifest.SimpleHead
err := yaml.Unmarshal([]byte(testManifest), &sh) err := yaml.Unmarshal([]byte(testManifest), &sh)
if err != nil { if err != nil {
return nil, err return nil, err

@ -19,20 +19,17 @@ package releaseutil
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strconv"
"strings" "strings"
)
// SimpleHead defines what the structure of the head of a manifest file "k8s.io/helm/pkg/manifest"
type SimpleHead struct { )
Version string `json:"apiVersion"`
Kind string `json:"kind,omitempty"`
Metadata *struct {
Name string `json:"name"`
Annotations map[string]string `json:"annotations"`
} `json:"metadata,omitempty"`
}
var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") var (
sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*")
wei = regexp.MustCompile("\\#\\s*Weight\\:\\s*(\\d+),(\\d+)\\s*")
path = regexp.MustCompile("\\#\\s*Source\\:\\s*(.*)\\s*")
)
// SplitManifests takes a string of manifest and returns a map contains individual manifests // SplitManifests takes a string of manifest and returns a map contains individual manifests
func SplitManifests(bigFile string) map[string]string { func SplitManifests(bigFile string) map[string]string {
@ -45,6 +42,7 @@ func SplitManifests(bigFile string) map[string]string {
bigFileTmp := strings.TrimSpace(bigFile) bigFileTmp := strings.TrimSpace(bigFile)
docs := sep.Split(bigFileTmp, -1) docs := sep.Split(bigFileTmp, -1)
var count int var count int
var name string
for _, d := range docs { for _, d := range docs {
if d == "" { if d == "" {
@ -52,8 +50,60 @@ func SplitManifests(bigFile string) map[string]string {
} }
d = strings.TrimSpace(d) d = strings.TrimSpace(d)
res[fmt.Sprintf(tpl, count)] = d match := path.FindStringSubmatch(d)
if match != nil {
name = match[1]
} else {
name = fmt.Sprintf(tpl, count)
}
res[name] = d
count = count + 1 count = count + 1
} }
return res return res
} }
// SplitManifestContent takes a string of manifest and returns a slice containing individual manifests
func SplitManifestContent(bigFile string) []string {
res := []string{}
bigFileTmp := strings.TrimSpace(bigFile)
docs := sep.Split(bigFileTmp, -1)
for _, d := range docs {
if d == "" {
continue
}
res = append(res, d)
}
return res
}
// GroupManifestsByWeight takes a map of manifests and returns an ordered grouping of them
func GroupManifestsByWeight(manifests []string) []string {
groups := []string{}
var groupWeight *manifest.Weight
var weight *manifest.Weight
for _, m := range manifests {
weight = getManifestWeight(m)
tmp := fmt.Sprintf("---\n%s\n", m)
if weight.Equalto(groupWeight) {
groups[len(groups)-1] += tmp
} else {
groups = append(groups, tmp)
groupWeight = weight
}
}
return groups
}
func getManifestWeight(file string) (weight *manifest.Weight) {
weight = new(manifest.Weight)
match := wei.FindStringSubmatch(file)
if match == nil {
return
}
c, _ := strconv.ParseUint(match[1], 10, 32)
m, _ := strconv.ParseUint(match[2], 10, 32)
weight.Chart = uint32(c)
weight.Manifest = uint32(m)
return
}

@ -156,6 +156,8 @@ type KubeClient interface {
// by "\n---\n"). // by "\n---\n").
UpdateWithOptions(namespace string, originalReader, modifiedReader io.Reader, opts kube.UpdateOptions) error UpdateWithOptions(namespace string, originalReader, modifiedReader io.Reader, opts kube.UpdateOptions) error
RemoveDiff(namespace string, originalReader, targetReader io.Reader) error
Build(namespace string, reader io.Reader) (kube.Result, error) Build(namespace string, reader io.Reader) (kube.Result, error)
// BuildUnstructured reads a stream of manifests from a reader and turns them into // BuildUnstructured reads a stream of manifests from a reader and turns them into
@ -234,6 +236,12 @@ func (p *PrintingKubeClient) UpdateWithOptions(ns string, currentReader, modifie
return err return err
} }
// RemoveDiff implements KubeClient RemoveDiff.
func (p *PrintingKubeClient) RemoveDiff(namespace string, originalReader, targetReader io.Reader) error {
_, err := io.Copy(p.Out, targetReader)
return err
}
// Build implements KubeClient Build. // Build implements KubeClient Build.
func (p *PrintingKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) { func (p *PrintingKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) {
return []*resource.Info{}, nil return []*resource.Info{}, nil

@ -61,6 +61,9 @@ func (k *mockKubeClient) UpdateWithOptions(ns string, currentReader, modifiedRea
func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error { func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error {
return nil return nil
} }
func (k *mockKubeClient) RemoveDiff(namespace string, originalReader, targetReader io.Reader) error {
return nil
}
func (k *mockKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) { func (k *mockKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) {
return []*resource.Info{}, nil return []*resource.Info{}, nil
} }

@ -19,6 +19,7 @@ package tiller
import ( import (
"fmt" "fmt"
"log" "log"
"os"
"path" "path"
"strconv" "strconv"
"strings" "strings"
@ -28,10 +29,21 @@ import (
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/manifest" "k8s.io/helm/pkg/manifest"
"k8s.io/helm/pkg/proto/hapi/chart"
"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"
) )
// SortType is used for determining sort function
type SortType int
const (
// SortInstall is used for installing a chart
SortInstall SortType = iota
// SortUninstall is used for removing a chart
SortUninstall
)
var events = map[string]release.Hook_Event{ var events = map[string]release.Hook_Event{
hooks.PreInstall: release.Hook_PRE_INSTALL, hooks.PreInstall: release.Hook_PRE_INSTALL,
hooks.PostInstall: release.Hook_POST_INSTALL, hooks.PostInstall: release.Hook_POST_INSTALL,
@ -79,7 +91,7 @@ type manifestFile 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(ch *chart.Chart, files map[string]string, apis chartutil.VersionSet, sort SortType) ([]*release.Hook, []Manifest, error) {
result := &result{} result := &result{}
for filePath, c := range files { for filePath, c := range files {
@ -101,12 +113,12 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort
apis: apis, apis: apis,
} }
if err := manifestFile.sort(result); err != nil { if err := manifestFile.sort(ch, result); err != nil {
return result.hooks, result.generic, err return result.hooks, result.generic, err
} }
} }
return result.hooks, sortByKind(result.generic, sort), nil return result.hooks, sortByWeight(sortByKind(result.generic, sort), sort), nil
} }
// sort takes a manifestFile object which may contain multiple resource definition // sort takes a manifestFile object which may contain multiple resource definition
@ -128,9 +140,9 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort
// metadata: // metadata:
// annotations: // annotations:
// helm.sh/hook-delete-policy: hook-succeeded // helm.sh/hook-delete-policy: hook-succeeded
func (file *manifestFile) sort(result *result) error { func (file *manifestFile) sort(ch *chart.Chart, result *result) error {
for _, m := range file.entries { for _, m := range file.entries {
var entry util.SimpleHead var entry manifest.SimpleHead
err := yaml.Unmarshal([]byte(m), &entry) err := yaml.Unmarshal([]byte(m), &entry)
if err != nil { if err != nil {
@ -138,21 +150,35 @@ func (file *manifestFile) sort(result *result) error {
return e return e
} }
var weight manifest.Weight
if ch != nil {
weight = manifest.Weight{
Chart: getChartWeight(ch, file.path),
Manifest: 0,
}
}
if !hasAnyAnnotation(entry) { if !hasAnyAnnotation(entry) {
result.generic = append(result.generic, Manifest{ result.generic = append(result.generic, Manifest{
Name: file.path, Name: file.path,
Content: m, Content: m,
Head: &entry, Head: &entry,
Weight: &weight,
}) })
continue continue
} }
if mw, err := strconv.ParseUint(entry.Metadata.Annotations[manifest.ManifestOrderWeight], 10, 32); err == nil {
weight.Manifest = uint32(mw)
}
hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno] hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno]
if !ok { if !ok {
result.generic = append(result.generic, Manifest{ result.generic = append(result.generic, Manifest{
Name: file.path, Name: file.path,
Content: m, Content: m,
Head: &entry, Head: &entry,
Weight: &weight,
}) })
continue continue
} }
@ -211,7 +237,35 @@ func (file *manifestFile) sort(result *result) error {
return nil return nil
} }
func hasAnyAnnotation(entry util.SimpleHead) bool { func getChartWeight(ch *chart.Chart, name string) uint32 {
if ch == nil {
return 0
}
for _, tpl := range ch.Templates {
if path.Base(tpl.Name) == path.Base(name) && ch.Metadata.Name == getOwnerChart(name) {
return ch.Metadata.Weight
}
}
for _, chart := range ch.Dependencies {
if w := getChartWeight(chart, name); w != 0 {
return w
}
}
return 0
}
func getOwnerChart(path string) string {
parts := strings.Split(path, string(os.PathSeparator))
if len(parts) >= 3 {
return parts[len(parts)-3]
}
return ""
}
func hasAnyAnnotation(entry manifest.SimpleHead) bool {
if entry.Metadata == nil || if entry.Metadata == nil ||
entry.Metadata.Annotations == nil || entry.Metadata.Annotations == nil ||
len(entry.Metadata.Annotations) == 0 { len(entry.Metadata.Annotations) == 0 {
@ -221,7 +275,7 @@ func hasAnyAnnotation(entry util.SimpleHead) bool {
return true return true
} }
func calculateHookWeight(entry util.SimpleHead) int32 { func calculateHookWeight(entry manifest.SimpleHead) int32 {
hws := entry.Metadata.Annotations[hooks.HookWeightAnno] hws := entry.Metadata.Annotations[hooks.HookWeightAnno]
hw, err := strconv.Atoi(hws) hw, err := strconv.Atoi(hws)
if err != nil { if err != nil {
@ -231,7 +285,7 @@ func calculateHookWeight(entry util.SimpleHead) int32 {
return int32(hw) return int32(hw)
} }
func operateAnnotationValues(entry util.SimpleHead, annotation string, operate func(p string)) { func operateAnnotationValues(entry manifest.SimpleHead, annotation string, operate func(p string)) {
if dps, ok := entry.Metadata.Annotations[annotation]; ok { if dps, ok := entry.Metadata.Annotations[annotation]; ok {
for _, dp := range strings.Split(dps, ",") { for _, dp := range strings.Split(dps, ",") {
dp = strings.ToLower(strings.TrimSpace(dp)) dp = strings.ToLower(strings.TrimSpace(dp))

@ -25,6 +25,7 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/manifest"
"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"
) )
@ -142,7 +143,7 @@ metadata:
manifests[o.path] = o.manifest manifests[o.path] = o.manifest
} }
hs, generic, err := sortManifests(manifests, chartutil.NewVersionSet("v1", "v1beta1"), InstallOrder) hs, generic, err := sortManifests(nil, manifests, chartutil.NewVersionSet("v1", "v1beta1"), SortInstall)
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %s", err) t.Fatalf("Unexpected error: %s", err)
} }
@ -201,7 +202,7 @@ metadata:
manifests := util.SplitManifests(s.manifest) manifests := util.SplitManifests(s.manifest)
for _, m := range manifests { for _, m := range manifests {
var sh util.SimpleHead var sh manifest.SimpleHead
err := yaml.Unmarshal([]byte(m), &sh) err := yaml.Unmarshal([]byte(m), &sh)
if err != nil { if err != nil {
// This is expected for manifests that are corrupt or empty. // This is expected for manifests that are corrupt or empty.
@ -223,7 +224,7 @@ metadata:
} }
} }
sorted = sortByKind(sorted, InstallOrder) sorted = sortByKind(sorted, SortInstall)
for i, m := range generic { for i, m := range generic {
if m.Content != sorted[i].Content { if m.Content != sorted[i].Content {
t.Errorf("Expected %q, got %q", m.Content, sorted[i].Content) t.Errorf("Expected %q, got %q", m.Content, sorted[i].Content)
@ -304,7 +305,7 @@ func TestSortManifestsHookDeletion(t *testing.T) {
"exampleManifest": buf.String(), "exampleManifest": buf.String(),
} }
hs, _, err := sortManifests(manifests, chartutil.NewVersionSet("v1", "v1beta1"), InstallOrder) hs, _, err := sortManifests(nil, manifests, chartutil.NewVersionSet("v1", "v1beta1"), SortInstall)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

@ -102,7 +102,13 @@ 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(manifests []Manifest, st SortType) []Manifest {
var ordering SortOrder
if st == SortInstall {
ordering = InstallOrder
} else {
ordering = UninstallOrder
}
ks := newKindSorter(manifests, ordering) ks := newKindSorter(manifests, ordering)
sort.Sort(ks) sort.Sort(ks)
return ks.manifests return ks.manifests

@ -20,152 +20,152 @@ import (
"bytes" "bytes"
"testing" "testing"
util "k8s.io/helm/pkg/releaseutil" "k8s.io/helm/pkg/manifest"
) )
func TestKindSorter(t *testing.T) { func TestKindSorter(t *testing.T) {
manifests := []Manifest{ manifests := []Manifest{
{ {
Name: "i", Name: "i",
Head: &util.SimpleHead{Kind: "ClusterRole"}, Head: &manifest.SimpleHead{Kind: "ClusterRole"},
}, },
{ {
Name: "I", Name: "I",
Head: &util.SimpleHead{Kind: "ClusterRoleList"}, Head: &manifest.SimpleHead{Kind: "ClusterRoleList"},
}, },
{ {
Name: "j", Name: "j",
Head: &util.SimpleHead{Kind: "ClusterRoleBinding"}, Head: &manifest.SimpleHead{Kind: "ClusterRoleBinding"},
}, },
{ {
Name: "J", Name: "J",
Head: &util.SimpleHead{Kind: "ClusterRoleBindingList"}, Head: &manifest.SimpleHead{Kind: "ClusterRoleBindingList"},
}, },
{ {
Name: "e", Name: "e",
Head: &util.SimpleHead{Kind: "ConfigMap"}, Head: &manifest.SimpleHead{Kind: "ConfigMap"},
}, },
{ {
Name: "u", Name: "u",
Head: &util.SimpleHead{Kind: "CronJob"}, Head: &manifest.SimpleHead{Kind: "CronJob"},
}, },
{ {
Name: "2", Name: "2",
Head: &util.SimpleHead{Kind: "CustomResourceDefinition"}, Head: &manifest.SimpleHead{Kind: "CustomResourceDefinition"},
}, },
{ {
Name: "n", Name: "n",
Head: &util.SimpleHead{Kind: "DaemonSet"}, Head: &manifest.SimpleHead{Kind: "DaemonSet"},
}, },
{ {
Name: "r", Name: "r",
Head: &util.SimpleHead{Kind: "Deployment"}, Head: &manifest.SimpleHead{Kind: "Deployment"},
}, },
{ {
Name: "!", Name: "!",
Head: &util.SimpleHead{Kind: "HonkyTonkSet"}, Head: &manifest.SimpleHead{Kind: "HonkyTonkSet"},
}, },
{ {
Name: "v", Name: "v",
Head: &util.SimpleHead{Kind: "Ingress"}, Head: &manifest.SimpleHead{Kind: "Ingress"},
}, },
{ {
Name: "t", Name: "t",
Head: &util.SimpleHead{Kind: "Job"}, Head: &manifest.SimpleHead{Kind: "Job"},
}, },
{ {
Name: "c", Name: "c",
Head: &util.SimpleHead{Kind: "LimitRange"}, Head: &manifest.SimpleHead{Kind: "LimitRange"},
}, },
{ {
Name: "a", Name: "a",
Head: &util.SimpleHead{Kind: "Namespace"}, Head: &manifest.SimpleHead{Kind: "Namespace"},
}, },
{ {
Name: "f", Name: "f",
Head: &util.SimpleHead{Kind: "PersistentVolume"}, Head: &manifest.SimpleHead{Kind: "PersistentVolume"},
}, },
{ {
Name: "g", Name: "g",
Head: &util.SimpleHead{Kind: "PersistentVolumeClaim"}, Head: &manifest.SimpleHead{Kind: "PersistentVolumeClaim"},
}, },
{ {
Name: "o", Name: "o",
Head: &util.SimpleHead{Kind: "Pod"}, Head: &manifest.SimpleHead{Kind: "Pod"},
}, },
{ {
Name: "3", Name: "3",
Head: &util.SimpleHead{Kind: "PodSecurityPolicy"}, Head: &manifest.SimpleHead{Kind: "PodSecurityPolicy"},
}, },
{ {
Name: "q", Name: "q",
Head: &util.SimpleHead{Kind: "ReplicaSet"}, Head: &manifest.SimpleHead{Kind: "ReplicaSet"},
}, },
{ {
Name: "p", Name: "p",
Head: &util.SimpleHead{Kind: "ReplicationController"}, Head: &manifest.SimpleHead{Kind: "ReplicationController"},
}, },
{ {
Name: "b", Name: "b",
Head: &util.SimpleHead{Kind: "ResourceQuota"}, Head: &manifest.SimpleHead{Kind: "ResourceQuota"},
}, },
{ {
Name: "k", Name: "k",
Head: &util.SimpleHead{Kind: "Role"}, Head: &manifest.SimpleHead{Kind: "Role"},
}, },
{ {
Name: "K", Name: "K",
Head: &util.SimpleHead{Kind: "RoleList"}, Head: &manifest.SimpleHead{Kind: "RoleList"},
}, },
{ {
Name: "l", Name: "l",
Head: &util.SimpleHead{Kind: "RoleBinding"}, Head: &manifest.SimpleHead{Kind: "RoleBinding"},
}, },
{ {
Name: "L", Name: "L",
Head: &util.SimpleHead{Kind: "RoleBindingList"}, Head: &manifest.SimpleHead{Kind: "RoleBindingList"},
}, },
{ {
Name: "d", Name: "d",
Head: &util.SimpleHead{Kind: "Secret"}, Head: &manifest.SimpleHead{Kind: "Secret"},
}, },
{ {
Name: "m", Name: "m",
Head: &util.SimpleHead{Kind: "Service"}, Head: &manifest.SimpleHead{Kind: "Service"},
}, },
{ {
Name: "h", Name: "h",
Head: &util.SimpleHead{Kind: "ServiceAccount"}, Head: &manifest.SimpleHead{Kind: "ServiceAccount"},
}, },
{ {
Name: "s", Name: "s",
Head: &util.SimpleHead{Kind: "StatefulSet"}, Head: &manifest.SimpleHead{Kind: "StatefulSet"},
}, },
{ {
Name: "1", Name: "1",
Head: &util.SimpleHead{Kind: "StorageClass"}, Head: &manifest.SimpleHead{Kind: "StorageClass"},
}, },
{ {
Name: "w", Name: "w",
Head: &util.SimpleHead{Kind: "APIService"}, Head: &manifest.SimpleHead{Kind: "APIService"},
}, },
{ {
Name: "z", Name: "z",
Head: &util.SimpleHead{Kind: "PodDisruptionBudget"}, Head: &manifest.SimpleHead{Kind: "PodDisruptionBudget"},
}, },
{ {
Name: "x", Name: "x",
Head: &util.SimpleHead{Kind: "HorizontalPodAutoscaler"}, Head: &manifest.SimpleHead{Kind: "HorizontalPodAutoscaler"},
}, },
} }
for _, test := range []struct { for _, test := range []struct {
description string description string
order SortOrder order SortType
expected string expected string
}{ }{
{"install", InstallOrder, "abc3zde1fgh2iIjJkKlLmnopqrxstuvw!"}, {"install", SortInstall, "abc3zde1fgh2iIjJkKlLmnopqrxstuvw!"},
{"uninstall", UninstallOrder, "wvmutsxrqponLlKkJjIi2hgf1edz3cba!"}, {"uninstall", SortUninstall, "wvmutsxrqponLlKkJjIi2hgf1edz3cba!"},
} { } {
var buf bytes.Buffer var buf bytes.Buffer
t.Run(test.description, func(t *testing.T) { t.Run(test.description, func(t *testing.T) {
@ -188,48 +188,48 @@ func TestKindSorterSubSort(t *testing.T) {
manifests := []Manifest{ manifests := []Manifest{
{ {
Name: "a", Name: "a",
Head: &util.SimpleHead{Kind: "ClusterRole"}, Head: &manifest.SimpleHead{Kind: "ClusterRole"},
}, },
{ {
Name: "A", Name: "A",
Head: &util.SimpleHead{Kind: "ClusterRole"}, Head: &manifest.SimpleHead{Kind: "ClusterRole"},
}, },
{ {
Name: "0", Name: "0",
Head: &util.SimpleHead{Kind: "ConfigMap"}, Head: &manifest.SimpleHead{Kind: "ConfigMap"},
}, },
{ {
Name: "1", Name: "1",
Head: &util.SimpleHead{Kind: "ConfigMap"}, Head: &manifest.SimpleHead{Kind: "ConfigMap"},
}, },
{ {
Name: "z", Name: "z",
Head: &util.SimpleHead{Kind: "ClusterRoleBinding"}, Head: &manifest.SimpleHead{Kind: "ClusterRoleBinding"},
}, },
{ {
Name: "!", Name: "!",
Head: &util.SimpleHead{Kind: "ClusterRoleBinding"}, Head: &manifest.SimpleHead{Kind: "ClusterRoleBinding"},
}, },
{ {
Name: "u2", Name: "u2",
Head: &util.SimpleHead{Kind: "Unknown"}, Head: &manifest.SimpleHead{Kind: "Unknown"},
}, },
{ {
Name: "u1", Name: "u1",
Head: &util.SimpleHead{Kind: "Unknown"}, Head: &manifest.SimpleHead{Kind: "Unknown"},
}, },
{ {
Name: "t3", Name: "t3",
Head: &util.SimpleHead{Kind: "Unknown2"}, Head: &manifest.SimpleHead{Kind: "Unknown2"},
}, },
} }
for _, test := range []struct { for _, test := range []struct {
description string description string
order SortOrder order SortType
expected string expected string
}{ }{
// expectation is sorted by kind (unknown is last) and then sub sorted alphabetically within each group // expectation is sorted by kind (unknown is last) and then sub sorted alphabetically within each group
{"cm,clusterRole,clusterRoleBinding,Unknown,Unknown2", InstallOrder, "01Aa!zu1u2t3"}, {"cm,clusterRole,clusterRoleBinding,Unknown,Unknown2", SortInstall, "01Aa!zu1u2t3"},
} { } {
var buf bytes.Buffer var buf bytes.Buffer
t.Run(test.description, func(t *testing.T) { t.Run(test.description, func(t *testing.T) {
@ -247,15 +247,15 @@ func TestKindSorterSubSort(t *testing.T) {
func TestKindSorterNamespaceAgainstUnknown(t *testing.T) { func TestKindSorterNamespaceAgainstUnknown(t *testing.T) {
unknown := Manifest{ unknown := Manifest{
Name: "a", Name: "a",
Head: &util.SimpleHead{Kind: "Unknown"}, Head: &manifest.SimpleHead{Kind: "Unknown"},
} }
namespace := Manifest{ namespace := Manifest{
Name: "b", Name: "b",
Head: &util.SimpleHead{Kind: "Namespace"}, Head: &manifest.SimpleHead{Kind: "Namespace"},
} }
manifests := []Manifest{unknown, namespace} manifests := []Manifest{unknown, namespace}
sortByKind(manifests, InstallOrder) sortByKind(manifests, SortInstall)
expectedOrder := []Manifest{namespace, unknown} expectedOrder := []Manifest{namespace, unknown}
for i, manifest := range manifests { for i, manifest := range manifests {

@ -105,7 +105,7 @@ func TestInstallRelease(t *testing.T) {
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.Manifest, "---\n# Source: hello/templates/hello\n# Weight: 0,0\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest) t.Errorf("unexpected output: %s", rel.Manifest)
} }
@ -165,7 +165,7 @@ func TestInstallRelease_WithNotes(t *testing.T) {
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.Manifest, "---\n# Source: hello/templates/hello\n# Weight: 0,0\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest) t.Errorf("unexpected output: %s", rel.Manifest)
} }
@ -226,7 +226,7 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) {
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.Manifest, "---\n# Source: hello/templates/hello\n# Weight: 0,0\nhello: world") {
t.Errorf("unexpected output: %s", rel.Manifest) t.Errorf("unexpected output: %s", rel.Manifest)
} }
@ -348,11 +348,11 @@ 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.Manifest, "---\n# Source: hello/templates/hello\n# Weight: 0,0\nhello: world") {
t.Errorf("unexpected output: %s", res.Release.Manifest) t.Errorf("unexpected output: %s", res.Release.Manifest)
} }
if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world") { if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/templates/goodbye\n# Weight: 0,0\ngoodbye: world") {
t.Errorf("unexpected output: %s", res.Release.Manifest) t.Errorf("unexpected output: %s", res.Release.Manifest)
} }

@ -50,34 +50,108 @@ type LocalReleaseModule struct {
// Create creates a release via kubeclient from provided environment // Create creates a release via kubeclient from provided environment
func (m *LocalReleaseModule) Create(r *release.Release, req *services.InstallReleaseRequest, env *environment.Environment) error { func (m *LocalReleaseModule) Create(r *release.Release, req *services.InstallReleaseRequest, env *environment.Environment) error {
b := bytes.NewBufferString(r.Manifest) if !req.Wait {
return env.KubeClient.Create(r.Namespace, b, req.Timeout, req.Wait) b := bytes.NewBufferString(r.Manifest)
return env.KubeClient.Create(r.Namespace, b, req.Timeout, req.Wait)
}
manifests := relutil.GroupManifestsByWeight(relutil.SplitManifestContent(r.Manifest))
for _, manifest := range manifests {
b := bytes.NewBufferString(manifest)
if err := env.KubeClient.Create(r.Namespace, b, req.Timeout, req.Wait); err != nil {
return err
}
}
return nil
} }
// Update performs an update from current to target release // Update performs an update from current to target release
func (m *LocalReleaseModule) Update(current, target *release.Release, req *services.UpdateReleaseRequest, env *environment.Environment) error { func (m *LocalReleaseModule) Update(current, target *release.Release, req *services.UpdateReleaseRequest, env *environment.Environment) error {
c := bytes.NewBufferString(current.Manifest) var err error
t := bytes.NewBufferString(target.Manifest) var t *bytes.Buffer
return env.KubeClient.UpdateWithOptions(target.Namespace, c, t, kube.UpdateOptions{ var c *bytes.Buffer
Force: req.Force,
Recreate: req.Recreate, if !req.Wait {
Timeout: req.Timeout, c := bytes.NewBufferString(current.Manifest)
ShouldWait: req.Wait, t := bytes.NewBufferString(target.Manifest)
CleanupOnFail: req.CleanupOnFail, if err = env.KubeClient.UpdateWithOptions(target.Namespace, c, t, kube.UpdateOptions{
}) Force: req.Force,
Recreate: req.Recreate,
Timeout: req.Timeout,
ShouldWait: req.Wait,
CleanupOnFail: req.CleanupOnFail,
}); err != nil {
return err
}
} else {
manifests := relutil.GroupManifestsByWeight(relutil.SplitManifestContent(target.Manifest))
for _, manifest := range manifests {
c = bytes.NewBufferString(current.Manifest)
t = bytes.NewBufferString(manifest)
if err = env.KubeClient.UpdateWithOptions(target.Namespace, c, t, kube.UpdateOptions{
Force: req.Force,
Recreate: req.Recreate,
Timeout: req.Timeout,
ShouldWait: req.Wait,
CleanupOnFail: req.CleanupOnFail,
}); err != nil {
return err
}
}
}
c = bytes.NewBufferString(current.Manifest)
t = bytes.NewBufferString(target.Manifest)
if err = env.KubeClient.RemoveDiff(target.Namespace, c, t); err != nil {
return err
}
return nil
} }
// Rollback performs a rollback from current to target release // Rollback performs a rollback from current to target release
func (m *LocalReleaseModule) Rollback(current, target *release.Release, req *services.RollbackReleaseRequest, env *environment.Environment) error { func (m *LocalReleaseModule) Rollback(current, target *release.Release, req *services.RollbackReleaseRequest, env *environment.Environment) error {
c := bytes.NewBufferString(current.Manifest) var err error
t := bytes.NewBufferString(target.Manifest) var t *bytes.Buffer
return env.KubeClient.UpdateWithOptions(target.Namespace, c, t, kube.UpdateOptions{ var c *bytes.Buffer
Force: req.Force,
Recreate: req.Recreate, if !req.Wait {
Timeout: req.Timeout, c := bytes.NewBufferString(current.Manifest)
ShouldWait: req.Wait, t := bytes.NewBufferString(target.Manifest)
CleanupOnFail: req.CleanupOnFail, if err = env.KubeClient.UpdateWithOptions(target.Namespace, c, t, kube.UpdateOptions{
}) Force: req.Force,
Recreate: req.Recreate,
Timeout: req.Timeout,
ShouldWait: req.Wait,
CleanupOnFail: req.CleanupOnFail,
}); err != nil {
return err
}
} else {
manifests := relutil.GroupManifestsByWeight(relutil.SplitManifestContent(target.Manifest))
for _, manifest := range manifests {
c = bytes.NewBufferString(current.Manifest)
t = bytes.NewBufferString(manifest)
if err = env.KubeClient.UpdateWithOptions(target.Namespace, c, t, kube.UpdateOptions{
Force: req.Force,
Recreate: req.Recreate,
Timeout: req.Timeout,
ShouldWait: req.Wait,
CleanupOnFail: req.CleanupOnFail,
}); err != nil {
return err
}
}
}
c = bytes.NewBufferString(current.Manifest)
t = bytes.NewBufferString(target.Manifest)
if err = env.KubeClient.RemoveDiff(target.Namespace, c, t); err != nil {
return err
}
return nil
} }
// Status returns kubectl-like formatted status of release objects // Status returns kubectl-like formatted status of release objects
@ -161,7 +235,7 @@ func (m *RemoteReleaseModule) Delete(r *release.Release, req *services.Uninstall
// DeleteRelease is a helper that allows Rudder to delete a release without exposing most of Tiller inner functions // DeleteRelease is a helper that allows Rudder to delete a release without exposing most of Tiller inner functions
func DeleteRelease(rel *release.Release, vs chartutil.VersionSet, kubeClient environment.KubeClient) (kept string, errs []error) { func DeleteRelease(rel *release.Release, vs chartutil.VersionSet, kubeClient environment.KubeClient) (kept string, errs []error) {
manifests := relutil.SplitManifests(rel.Manifest) manifests := relutil.SplitManifests(rel.Manifest)
_, files, err := sortManifests(manifests, vs, UninstallOrder) _, files, err := sortManifests(rel.Chart, manifests, vs, SortUninstall)
if err != nil { if err != nil {
// We could instead just delete everything in no particular order. // We could instead just delete everything in no particular order.
// FIXME: One way to delete at this point would be to try a label-based // FIXME: One way to delete at this point would be to try a label-based

@ -383,7 +383,7 @@ 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, manifests, err := sortManifests(ch, files, vs, SortInstall)
if err != nil { if err != nil {
// By catching parse errors here, we can prevent bogus releases from going // By catching parse errors here, we can prevent bogus releases from going
// to Kubernetes. // to Kubernetes.
@ -405,6 +405,7 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
for _, m := range manifests { for _, m := range manifests {
b.WriteString("\n---\n# Source: " + m.Name + "\n") b.WriteString("\n---\n# Source: " + m.Name + "\n")
b.WriteString(fmt.Sprintf("# Weight: %d,%d\n", m.Weight.Chart, m.Weight.Manifest))
b.WriteString(m.Content) b.WriteString(m.Content)
} }

@ -665,6 +665,9 @@ func (kc *mockHooksKubeClient) Update(ns string, currentReader, modifiedReader i
func (kc *mockHooksKubeClient) UpdateWithOptions(ns string, currentReader, modifiedReader io.Reader, opts kube.UpdateOptions) error { func (kc *mockHooksKubeClient) UpdateWithOptions(ns string, currentReader, modifiedReader io.Reader, opts kube.UpdateOptions) error {
return nil return nil
} }
func (kc *mockHooksKubeClient) RemoveDiff(namespace string, originalReader, targetReader io.Reader) error {
return nil
}
func (kc *mockHooksKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) { func (kc *mockHooksKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) {
return []*resource.Info{}, nil return []*resource.Info{}, nil
} }

@ -91,7 +91,7 @@ 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 !strings.Contains(updated.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { if !strings.Contains(updated.Manifest, "---\n# Source: hello/templates/hello\n# Weight: 0,0\nhello: world") {
t.Errorf("unexpected output: %s", updated.Manifest) t.Errorf("unexpected output: %s", updated.Manifest)
} }

@ -0,0 +1,56 @@
/*
Copyright The Helm Authors.
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 (
"sort"
)
// sortByWeight does an in-place sort of manifests by Kind.
//
// Results are sorted by weight
func sortByWeight(manifests []Manifest, st SortType) []Manifest {
ws := newWeightSorter(manifests, st)
sort.Stable(ws)
return ws.manifests
}
type weightSorter struct {
manifests []Manifest
stype SortType
}
func newWeightSorter(m []Manifest, t SortType) *weightSorter {
return &weightSorter{
manifests: m,
stype: t,
}
}
func (w *weightSorter) Len() int { return len(w.manifests) }
func (w *weightSorter) Swap(i, j int) { w.manifests[i], w.manifests[j] = w.manifests[j], w.manifests[i] }
func (w *weightSorter) Less(i, j int) bool {
a := w.manifests[i]
b := w.manifests[j]
if w.stype == SortInstall {
return a.Weight.GreaterThan(b.Weight)
}
return a.Weight.LessThan(b.Weight)
}

@ -0,0 +1,108 @@
/*
Copyright The Helm Authors.
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"
"testing"
"k8s.io/helm/pkg/manifest"
)
func TestWeightSorter(t *testing.T) {
manifests := []Manifest{
{
Name: "a",
Weight: &manifest.Weight{
Chart: 1,
Manifest: 5,
},
},
{
Name: "u",
Weight: &manifest.Weight{
Chart: 0,
Manifest: 2,
},
},
{
Name: "d",
Weight: &manifest.Weight{
Chart: 1,
Manifest: 0,
},
},
{
Name: "e",
Weight: &manifest.Weight{
Chart: 2,
Manifest: 0,
},
},
{
Name: "t",
Weight: &manifest.Weight{
Chart: 10,
Manifest: 4294967295,
},
},
{
Name: "b",
Weight: &manifest.Weight{
Chart: 0,
Manifest: 0,
},
},
{
Name: "p",
Weight: &manifest.Weight{
Chart: 1,
Manifest: 5,
},
},
{
Name: "s",
Weight: &manifest.Weight{
Chart: 10,
Manifest: 10,
},
},
}
for _, test := range []struct {
description string
order SortType
expected string
}{
{"install", SortInstall, "tseapdub"},
{"uninstall", SortUninstall, "budapest"},
} {
var buf bytes.Buffer
t.Run(test.description, func(t *testing.T) {
if got, want := len(test.expected), len(manifests); got != want {
t.Fatalf("Expected %d names in order, got %d", want, got)
}
defer buf.Reset()
for _, r := range sortByWeight(manifests, test.order) {
buf.WriteString(r.Name)
}
if got := buf.String(); got != test.expected {
t.Errorf("Expected %q, got %q", test.expected, got)
}
})
}
}
Loading…
Cancel
Save