From 05d0fcb774d8134b49e60587f4dd7db98c8ef7b7 Mon Sep 17 00:00:00 2001 From: Jonathan Chauncey Date: Tue, 21 Mar 2017 15:21:24 -0400 Subject: [PATCH] feat(hooks): Adds weighted hooks closes #2136 * Adds new annotation `helm.sh/hookWeight` * Sorts executing hooks of similar kind in ascending order * There is no upper or lower bounds on the weights --- pkg/hooks/hooks.go | 3 ++ pkg/proto/hapi/release/hook.pb.go | 2 + pkg/tiller/hook_sorter.go | 53 ++++++++++++++++++++++ pkg/tiller/hook_sorter_test.go | 73 +++++++++++++++++++++++++++++++ pkg/tiller/hooks.go | 10 +++++ pkg/tiller/release_server.go | 14 +++--- 6 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 pkg/tiller/hook_sorter.go create mode 100644 pkg/tiller/hook_sorter_test.go diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go index ee467ea27..d6966a87a 100644 --- a/pkg/hooks/hooks.go +++ b/pkg/hooks/hooks.go @@ -23,6 +23,9 @@ import ( // HookAnno is the label name for a hook const HookAnno = "helm.sh/hook" +// HookWeightAnno is the label name for a hook weight +const HookWeightAnno = "helm.sh/hookWeight" + // Types of hooks const ( PreInstall = "pre-install" diff --git a/pkg/proto/hapi/release/hook.pb.go b/pkg/proto/hapi/release/hook.pb.go index 956ca15f3..435d2c80e 100644 --- a/pkg/proto/hapi/release/hook.pb.go +++ b/pkg/proto/hapi/release/hook.pb.go @@ -100,6 +100,8 @@ type Hook struct { Events []Hook_Event `protobuf:"varint,5,rep,packed,name=events,enum=hapi.release.Hook_Event" json:"events,omitempty"` // LastRun indicates the date/time this was last run. LastRun *google_protobuf.Timestamp `protobuf:"bytes,6,opt,name=last_run,json=lastRun" json:"last_run,omitempty"` + // Weight indicates the sort order for execution among similar Hook types + Weight int `protobuf:"bytes,7,opt,name=weight" json:"weight,omitempty"` } func (m *Hook) Reset() { *m = Hook{} } diff --git a/pkg/tiller/hook_sorter.go b/pkg/tiller/hook_sorter.go new file mode 100644 index 000000000..42d546620 --- /dev/null +++ b/pkg/tiller/hook_sorter.go @@ -0,0 +1,53 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tiller + +import ( + "sort" + + "k8s.io/helm/pkg/proto/hapi/release" +) + +// sortByHookWeight does an in-place sort of hooks by their supplied weight. +func sortByHookWeight(hooks []*release.Hook) []*release.Hook { + hs := newHookWeightSorter(hooks) + sort.Sort(hs) + return hs.hooks +} + +type hookWeightSorter struct { + hooks []*release.Hook +} + +func newHookWeightSorter(h []*release.Hook) *hookWeightSorter { + return &hookWeightSorter{ + hooks: h, + } +} + +func (hs *hookWeightSorter) Len() int { return len(hs.hooks) } + +func (hs *hookWeightSorter) Swap(i, j int) { + hs.hooks[i], hs.hooks[j] = hs.hooks[j], hs.hooks[i] +} + +func (hs *hookWeightSorter) Less(i, j int) bool { + if hs.hooks[i].Weight == hs.hooks[j].Weight { + return hs.hooks[i].Name < hs.hooks[j].Name + } + return hs.hooks[i].Weight < hs.hooks[j].Weight +} diff --git a/pkg/tiller/hook_sorter_test.go b/pkg/tiller/hook_sorter_test.go new file mode 100644 index 000000000..ac5b9bf8d --- /dev/null +++ b/pkg/tiller/hook_sorter_test.go @@ -0,0 +1,73 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tiller + +import ( + "testing" + + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestHookSorter(t *testing.T) { + hooks := []*release.Hook{ + { + Name: "g", + Kind: "pre-install", + Weight: 99, + }, + { + Name: "f", + Kind: "pre-install", + Weight: 3, + }, + { + Name: "b", + Kind: "pre-install", + Weight: -3, + }, + { + Name: "e", + Kind: "pre-install", + Weight: 3, + }, + { + Name: "a", + Kind: "pre-install", + Weight: -10, + }, + { + Name: "c", + Kind: "pre-install", + Weight: 0, + }, + { + Name: "d", + Kind: "pre-install", + Weight: 3, + }, + } + + res := sortByHookWeight(hooks) + got := "" + expect := "abcdefg" + for _, r := range res { + got += r.Name + } + if got != expect { + t.Errorf("Expected %q, got %q", expect, got) + } +} diff --git a/pkg/tiller/hooks.go b/pkg/tiller/hooks.go index 0b5510b8c..b6dfeb9cd 100644 --- a/pkg/tiller/hooks.go +++ b/pkg/tiller/hooks.go @@ -20,6 +20,7 @@ import ( "fmt" "log" "path" + "strconv" "strings" "github.com/ghodss/yaml" @@ -109,12 +110,21 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort generic = append(generic, manifest{name: n, content: c, head: &sh}) continue } + + hw := 0 + hws, _ := sh.Metadata.Annotations[hooks.HookWeightAnno] + hw, err = strconv.Atoi(hws) + if err != nil { + hw = 0 + } + h := &release.Hook{ Name: sh.Metadata.Name, Kind: sh.Kind, Path: n, Manifest: c, Events: []release.Hook_Event{}, + Weight: hw, } isHook := false diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index 873d2335a..a73dd9a73 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -911,17 +911,18 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin } log.Printf("Executing %s hooks for %s", hook, name) + executingHooks := []*release.Hook{} for _, h := range hs { - found := false for _, e := range h.Events { if e == code { - found = true + executingHooks = append(executingHooks, h) } } - // If this doesn't implement the hook, skip it. - if !found { - continue - } + } + + executingHooks = sortByHookWeight(executingHooks) + + for _, h := range executingHooks { b := bytes.NewBufferString(h.Manifest) if err := kubeCli.Create(namespace, b, timeout, false); err != nil { @@ -937,6 +938,7 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin } h.LastRun = timeconv.Now() } + log.Printf("Hooks complete for %s %s", hook, name) return nil }