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

@ -90,4 +90,7 @@ message Metadata {
// KubeVersion is a SemVer constraint specifying the version of Kubernetes required.
string kubeVersion = 17;
// Deployment weight of this chart
uint32 weight = 18;
}

@ -131,31 +131,35 @@ func (r *ReleaseModuleServiceServer) RollbackRelease(ctx context.Context, in *ru
grpclog.Print("rollback")
c := bytes.NewBufferString(in.Current.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,
Recreate: in.Recreate,
Timeout: in.Timeout,
ShouldWait: in.Wait,
CleanupOnFail: in.CleanupOnFail,
})
}); err != nil {
return &rudderAPI.RollbackReleaseResponse{}, err
}
return &rudderAPI.RollbackReleaseResponse{}, nil
}
// UpgradeRelease upgrades manifests using kubernetes client
func (r *ReleaseModuleServiceServer) UpgradeRelease(ctx context.Context, in *rudderAPI.UpgradeReleaseRequest) (*rudderAPI.UpgradeReleaseResponse, error) {
grpclog.Print("upgrade")
c := bytes.NewBufferString(in.Current.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,
Recreate: in.Recreate,
Timeout: in.Timeout,
ShouldWait: in.Wait,
CleanupOnFail: in.CleanupOnFail,
})
}); err != nil {
// upgrade response object should be changed to include status
return &rudderAPI.UpgradeReleaseResponse{}, err
}
return &rudderAPI.UpgradeReleaseResponse{}, nil
}
// ReleaseStatus retrieves release status
func (r *ReleaseModuleServiceServer) ReleaseStatus(ctx context.Context, in *rudderAPI.ReleaseStatusRequest) (*rudderAPI.ReleaseStatusResponse, error) {

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

@ -68,6 +68,8 @@ type Dependency struct {
ImportValues []interface{} `json:"import-values,omitempty"`
// Alias usable alias to be used for the chart
Alias string `json:"alias,omitempty"`
// Deployment weight of this chart
Weight uint32 `json:"weight,omitempty"`
}
// 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...), " && "))
}
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) {
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)
}
}
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
}

@ -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 {
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
// Test with a wait
// 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 (
"regexp"
"strings"
"k8s.io/helm/pkg/releaseutil"
)
var (
@ -38,7 +36,7 @@ func SplitManifests(templates map[string]string) []Manifest {
if len(match) == 2 {
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)
}

@ -16,13 +16,29 @@ limitations under the License.
package manifest
import (
"k8s.io/helm/pkg/releaseutil"
)
// ManifestOrderWeight is the label name for a manifest
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.
type Manifest struct {
Name 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))
}
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.
@ -58,7 +58,7 @@ func (m *Maintainer) Reset() { *m = Maintainer{} }
func (m *Maintainer) String() string { return proto.CompactTextString(m) }
func (*Maintainer) ProtoMessage() {}
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 {
return xxx_messageInfo_Maintainer.Unmarshal(m, b)
@ -139,6 +139,8 @@ type Metadata struct {
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 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_unrecognized []byte `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 (*Metadata) ProtoMessage() {}
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 {
return xxx_messageInfo_Metadata.Unmarshal(m, b)
@ -287,6 +289,13 @@ func (m *Metadata) GetKubeVersion() string {
return ""
}
func (m *Metadata) GetWeight() uint32 {
if m != nil {
return m.Weight
}
return 0
}
func init() {
proto.RegisterType((*Maintainer)(nil), "hapi.chart.Maintainer")
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)
}
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{
// 435 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0x5d, 0x6b, 0xd4, 0x40,
0x14, 0x35, 0xcd, 0x66, 0x77, 0x73, 0x63, 0x35, 0x0e, 0x52, 0xc6, 0x22, 0x12, 0x16, 0x85, 0x7d,
var fileDescriptor_metadata_a633fda7c87700ff = []byte{
// 449 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0x5d, 0x6b, 0xd4, 0x40,
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,
0x2e, 0xdd, 0x61, 0x93, 0x99, 0x30, 0x99, 0xad, 0xec, 0xaf, 0xf0, 0x2f, 0xcb, 0xdc, 0x64, 0x9a,
0xac, 0xf4, 0xed, 0x9e, 0x73, 0x66, 0xce, 0xcc, 0xbd, 0xf7, 0xc0, 0x8b, 0x8d, 0x68, 0xe4, 0x59,
0xb1, 0x11, 0xc6, 0x9e, 0xd5, 0x68, 0x45, 0x29, 0xac, 0x58, 0x35, 0x46, 0x5b, 0xcd, 0xc0, 0x49,
0x2b, 0x92, 0x16, 0x9f, 0x01, 0xae, 0x84, 0x54, 0x56, 0x48, 0x85, 0x86, 0x31, 0x98, 0x28, 0x51,
0x23, 0x0f, 0xb2, 0x60, 0x19, 0xe7, 0x54, 0xb3, 0xe7, 0x10, 0x61, 0x2d, 0x64, 0xc5, 0x8f, 0x88,
0xec, 0x00, 0x4b, 0x21, 0xdc, 0x99, 0x8a, 0x87, 0xc4, 0xb9, 0x72, 0xf1, 0x37, 0x82, 0xf9, 0x55,
0xff, 0xd0, 0x83, 0x46, 0x0c, 0x26, 0x1b, 0x5d, 0x63, 0xef, 0x43, 0x35, 0xe3, 0x30, 0x6b, 0xf5,
0xce, 0x14, 0xd8, 0xf2, 0x30, 0x0b, 0x97, 0x71, 0xee, 0xa1, 0x53, 0xee, 0xd0, 0xb4, 0x52, 0x2b,
0x3e, 0xa1, 0x0b, 0x1e, 0xb2, 0x0c, 0x92, 0x12, 0xdb, 0xc2, 0xc8, 0xc6, 0x3a, 0x35, 0x22, 0x75,
0x4c, 0xb1, 0x53, 0x98, 0x6f, 0x71, 0xff, 0x47, 0x9b, 0xb2, 0xe5, 0x53, 0xb2, 0xbd, 0xc7, 0xec,
0x1c, 0x92, 0xfa, 0xbe, 0xe1, 0x96, 0xcf, 0xb2, 0x70, 0x99, 0xbc, 0x3d, 0x59, 0x0d, 0x23, 0x59,
0x0d, 0xf3, 0xc8, 0xc7, 0x47, 0xd9, 0x09, 0x4c, 0x51, 0xdd, 0x4a, 0x85, 0x7c, 0x4e, 0x4f, 0xf6,
0xc8, 0xf5, 0x25, 0x0b, 0xad, 0x78, 0xdc, 0xf5, 0xe5, 0x6a, 0xf6, 0x0a, 0x40, 0x34, 0xf2, 0x67,
0xdf, 0x00, 0x90, 0x32, 0x62, 0xd8, 0x4b, 0x88, 0x0b, 0xad, 0x4a, 0x49, 0x1d, 0x24, 0x24, 0x0f,
0x84, 0x73, 0xb4, 0xe2, 0xb6, 0xe5, 0x8f, 0x3b, 0x47, 0x57, 0x77, 0x8e, 0x8d, 0x77, 0x3c, 0xf6,
0x8e, 0x9e, 0x71, 0x7a, 0x89, 0x8d, 0xc1, 0x42, 0x58, 0x2c, 0xf9, 0x93, 0x2c, 0x58, 0xce, 0xf3,
0x11, 0xc3, 0x5e, 0xc3, 0xb1, 0x95, 0x55, 0x85, 0xc6, 0x5b, 0x3c, 0x25, 0x8b, 0x43, 0x92, 0x5d,
0x42, 0x22, 0x94, 0xd2, 0x56, 0xb8, 0x7f, 0xb4, 0x3c, 0xa5, 0xe9, 0xbc, 0x39, 0x98, 0x8e, 0xcf,
0xd2, 0xc7, 0xe1, 0xdc, 0x85, 0xb2, 0x66, 0x9f, 0x8f, 0x6f, 0xba, 0x25, 0x6d, 0x77, 0x37, 0xe8,
0x1f, 0x7b, 0xd6, 0x2d, 0x69, 0x44, 0x9d, 0x7e, 0x80, 0xf4, 0x7f, 0x0b, 0x97, 0xaa, 0x2d, 0xee,
0xfb, 0xd4, 0xb8, 0xd2, 0xa5, 0xef, 0x4e, 0x54, 0x3b, 0x9f, 0x9a, 0x0e, 0xbc, 0x3f, 0x3a, 0x0f,
0x16, 0x19, 0x4c, 0x2f, 0xba, 0x05, 0x24, 0x30, 0xfb, 0xb1, 0xfe, 0xb2, 0xbe, 0xfe, 0xb5, 0x4e,
0x1f, 0xb1, 0x18, 0xa2, 0xcb, 0xeb, 0xef, 0xdf, 0xbe, 0xa6, 0xc1, 0xa7, 0xd9, 0xef, 0x88, 0xfe,
0x7c, 0x33, 0xa5, 0xdc, 0xbf, 0xfb, 0x17, 0x00, 0x00, 0xff, 0xff, 0x36, 0xf9, 0x0d, 0xa6, 0x14,
0x03, 0x00, 0x00,
0x2e, 0xbb, 0xc3, 0x26, 0x33, 0x61, 0x32, 0xdb, 0xb2, 0xbf, 0xc8, 0xbf, 0x29, 0x33, 0xc9, 0x74,
0x53, 0xf1, 0xed, 0x9e, 0x73, 0x66, 0xce, 0xe4, 0xdc, 0x7b, 0x03, 0x2f, 0x36, 0xbc, 0x15, 0x67,
0xe5, 0x86, 0x6b, 0x73, 0xd6, 0x90, 0xe1, 0x15, 0x37, 0x7c, 0xd9, 0x6a, 0x65, 0x14, 0x82, 0x95,
0x96, 0x4e, 0x5a, 0x7c, 0x06, 0xb8, 0xe2, 0x42, 0x1a, 0x2e, 0x24, 0x69, 0x44, 0x38, 0x96, 0xbc,
0x21, 0x16, 0x64, 0x41, 0x1e, 0x17, 0xae, 0xc6, 0xe7, 0x10, 0x51, 0xc3, 0x45, 0xcd, 0x8e, 0x1c,
0xd9, 0x03, 0x4c, 0x21, 0xdc, 0xe9, 0x9a, 0x85, 0x8e, 0xb3, 0xe5, 0xe2, 0x4f, 0x04, 0xb3, 0xab,
0xe1, 0xa1, 0xff, 0x1a, 0x21, 0x1c, 0x6f, 0x54, 0x43, 0x83, 0x8f, 0xab, 0x91, 0xc1, 0xb4, 0x53,
0x3b, 0x5d, 0x52, 0xc7, 0xc2, 0x2c, 0xcc, 0xe3, 0xc2, 0x43, 0xab, 0xdc, 0x92, 0xee, 0x84, 0x92,
0xec, 0xd8, 0x5d, 0xf0, 0x10, 0x33, 0x48, 0x2a, 0xea, 0x4a, 0x2d, 0x5a, 0x63, 0xd5, 0xc8, 0xa9,
0x63, 0x0a, 0x4f, 0x61, 0xb6, 0xa5, 0xfd, 0x9d, 0xd2, 0x55, 0xc7, 0x26, 0xce, 0xf6, 0x1e, 0xe3,
0x39, 0x24, 0xcd, 0x7d, 0xe0, 0x8e, 0x4d, 0xb3, 0x30, 0x4f, 0xde, 0x9e, 0x2c, 0x0f, 0x2d, 0x59,
0x1e, 0xfa, 0x51, 0x8c, 0x8f, 0xe2, 0x09, 0x4c, 0x48, 0xae, 0x85, 0x24, 0x36, 0x73, 0x4f, 0x0e,
0xc8, 0xe6, 0x12, 0xa5, 0x92, 0x2c, 0xee, 0x73, 0xd9, 0x1a, 0x5f, 0x01, 0xf0, 0x56, 0xfc, 0x1c,
0x02, 0x80, 0x53, 0x46, 0x0c, 0xbe, 0x84, 0xb8, 0x54, 0xb2, 0x12, 0x2e, 0x41, 0xe2, 0xe4, 0x03,
0x61, 0x1d, 0x0d, 0x5f, 0x77, 0xec, 0x71, 0xef, 0x68, 0xeb, 0xde, 0xb1, 0xf5, 0x8e, 0x73, 0xef,
0xe8, 0x19, 0xab, 0x57, 0xd4, 0x6a, 0x2a, 0xb9, 0xa1, 0x8a, 0x3d, 0xc9, 0x82, 0x7c, 0x56, 0x8c,
0x18, 0x7c, 0x0d, 0x73, 0x23, 0xea, 0x9a, 0xb4, 0xb7, 0x78, 0xea, 0x2c, 0x1e, 0x92, 0x78, 0x09,
0x09, 0x97, 0x52, 0x19, 0x6e, 0xbf, 0xa3, 0x63, 0xa9, 0xeb, 0xce, 0x9b, 0x07, 0xdd, 0xf1, 0xbb,
0xf4, 0xf1, 0x70, 0xee, 0x42, 0x1a, 0xbd, 0x2f, 0xc6, 0x37, 0xed, 0x90, 0xb6, 0xbb, 0x1b, 0xf2,
0x8f, 0x3d, 0xeb, 0x87, 0x34, 0xa2, 0x6c, 0x3b, 0xef, 0x48, 0xac, 0x37, 0x86, 0x61, 0x16, 0xe4,
0xf3, 0x62, 0x40, 0xa7, 0x1f, 0x20, 0xfd, 0xd7, 0xda, 0x6e, 0xdb, 0x96, 0xf6, 0xc3, 0x36, 0xd9,
0xd2, 0x6e, 0xe5, 0x2d, 0xaf, 0x77, 0x7e, 0x9b, 0x7a, 0xf0, 0xfe, 0xe8, 0x3c, 0x58, 0x64, 0x30,
0xb9, 0xe8, 0x07, 0x93, 0xc0, 0xf4, 0xc7, 0xea, 0xcb, 0xea, 0xfa, 0xd7, 0x2a, 0x7d, 0x84, 0x31,
0x44, 0x97, 0xd7, 0xdf, 0xbf, 0x7d, 0x4d, 0x83, 0x4f, 0xd3, 0xdf, 0x91, 0xcb, 0x72, 0x33, 0x71,
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/helm/pkg/hooks"
"k8s.io/helm/pkg/manifest"
"k8s.io/helm/pkg/proto/hapi/release"
util "k8s.io/helm/pkg/releaseutil"
"k8s.io/helm/pkg/timeconv"
@ -206,7 +207,7 @@ func extractTestManifestsFromHooks(h []*release.Hook) ([]string, error) {
}
func newTest(testManifest string) (*test, error) {
var sh util.SimpleHead
var sh manifest.SimpleHead
err := yaml.Unmarshal([]byte(testManifest), &sh)
if err != nil {
return nil, err

@ -19,20 +19,17 @@ package releaseutil
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// 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"`
}
"k8s.io/helm/pkg/manifest"
)
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
func SplitManifests(bigFile string) map[string]string {
@ -45,6 +42,7 @@ func SplitManifests(bigFile string) map[string]string {
bigFileTmp := strings.TrimSpace(bigFile)
docs := sep.Split(bigFileTmp, -1)
var count int
var name string
for _, d := range docs {
if d == "" {
@ -52,8 +50,60 @@ func SplitManifests(bigFile string) map[string]string {
}
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
}
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").
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)
// 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
}
// 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.
func (p *PrintingKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) {
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 {
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) {
return []*resource.Info{}, nil
}

@ -19,6 +19,7 @@ package tiller
import (
"fmt"
"log"
"os"
"path"
"strconv"
"strings"
@ -28,10 +29,21 @@ import (
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/manifest"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release"
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{
hooks.PreInstall: release.Hook_PRE_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
// 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{}
for filePath, c := range files {
@ -101,12 +113,12 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort
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, 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
@ -128,9 +140,9 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort
// metadata:
// annotations:
// 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 {
var entry util.SimpleHead
var entry manifest.SimpleHead
err := yaml.Unmarshal([]byte(m), &entry)
if err != nil {
@ -138,21 +150,35 @@ func (file *manifestFile) sort(result *result) error {
return e
}
var weight manifest.Weight
if ch != nil {
weight = manifest.Weight{
Chart: getChartWeight(ch, file.path),
Manifest: 0,
}
}
if !hasAnyAnnotation(entry) {
result.generic = append(result.generic, Manifest{
Name: file.path,
Content: m,
Head: &entry,
Weight: &weight,
})
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]
if !ok {
result.generic = append(result.generic, Manifest{
Name: file.path,
Content: m,
Head: &entry,
Weight: &weight,
})
continue
}
@ -211,7 +237,35 @@ func (file *manifestFile) sort(result *result) error {
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 ||
entry.Metadata.Annotations == nil ||
len(entry.Metadata.Annotations) == 0 {
@ -221,7 +275,7 @@ func hasAnyAnnotation(entry util.SimpleHead) bool {
return true
}
func calculateHookWeight(entry util.SimpleHead) int32 {
func calculateHookWeight(entry manifest.SimpleHead) int32 {
hws := entry.Metadata.Annotations[hooks.HookWeightAnno]
hw, err := strconv.Atoi(hws)
if err != nil {
@ -231,7 +285,7 @@ func calculateHookWeight(entry util.SimpleHead) int32 {
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 {
for _, dp := range strings.Split(dps, ",") {
dp = strings.ToLower(strings.TrimSpace(dp))

@ -25,6 +25,7 @@ import (
"github.com/ghodss/yaml"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/manifest"
"k8s.io/helm/pkg/proto/hapi/release"
util "k8s.io/helm/pkg/releaseutil"
)
@ -142,7 +143,7 @@ metadata:
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 {
t.Fatalf("Unexpected error: %s", err)
}
@ -201,7 +202,7 @@ metadata:
manifests := util.SplitManifests(s.manifest)
for _, m := range manifests {
var sh util.SimpleHead
var sh manifest.SimpleHead
err := yaml.Unmarshal([]byte(m), &sh)
if err != nil {
// 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 {
if 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(),
}
hs, _, err := sortManifests(manifests, chartutil.NewVersionSet("v1", "v1beta1"), InstallOrder)
hs, _, err := sortManifests(nil, manifests, chartutil.NewVersionSet("v1", "v1beta1"), SortInstall)
if err != nil {
t.Error(err)
}

@ -102,7 +102,13 @@ var UninstallOrder SortOrder = []string{
// sortByKind does an in-place sort of manifests by Kind.
//
// 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)
sort.Sort(ks)
return ks.manifests

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

@ -105,7 +105,7 @@ func TestInstallRelease(t *testing.T) {
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)
}
@ -165,7 +165,7 @@ func TestInstallRelease_WithNotes(t *testing.T) {
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)
}
@ -226,7 +226,7 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) {
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)
}
@ -348,11 +348,11 @@ func TestInstallRelease_DryRun(t *testing.T) {
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)
}
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)
}

@ -50,34 +50,108 @@ type LocalReleaseModule struct {
// Create creates a release via kubeclient from provided environment
func (m *LocalReleaseModule) Create(r *release.Release, req *services.InstallReleaseRequest, env *environment.Environment) error {
if !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
func (m *LocalReleaseModule) Update(current, target *release.Release, req *services.UpdateReleaseRequest, env *environment.Environment) error {
var err error
var t *bytes.Buffer
var c *bytes.Buffer
if !req.Wait {
c := bytes.NewBufferString(current.Manifest)
t := bytes.NewBufferString(target.Manifest)
return env.KubeClient.UpdateWithOptions(target.Namespace, c, t, kube.UpdateOptions{
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
func (m *LocalReleaseModule) Rollback(current, target *release.Release, req *services.RollbackReleaseRequest, env *environment.Environment) error {
var err error
var t *bytes.Buffer
var c *bytes.Buffer
if !req.Wait {
c := bytes.NewBufferString(current.Manifest)
t := bytes.NewBufferString(target.Manifest)
return env.KubeClient.UpdateWithOptions(target.Namespace, c, t, kube.UpdateOptions{
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
@ -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
func DeleteRelease(rel *release.Release, vs chartutil.VersionSet, kubeClient environment.KubeClient) (kept string, errs []error) {
manifests := relutil.SplitManifests(rel.Manifest)
_, files, err := sortManifests(manifests, vs, UninstallOrder)
_, files, err := sortManifests(rel.Chart, manifests, vs, SortUninstall)
if err != nil {
// 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

@ -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,
// as partials are not used after renderer.Render. Empty manifests are also
// removed here.
hooks, manifests, err := sortManifests(files, vs, InstallOrder)
hooks, manifests, err := sortManifests(ch, files, vs, SortInstall)
if err != nil {
// By catching parse errors here, we can prevent bogus releases from going
// to Kubernetes.
@ -405,6 +405,7 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
b := bytes.NewBuffer(nil)
for _, m := range manifests {
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)
}

@ -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 {
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) {
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)
}
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)
}

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