From 261367e7e90ced072c91e986b586767a20b008c1 Mon Sep 17 00:00:00 2001 From: Javier Palacios Date: Tue, 6 Apr 2021 06:16:35 +0200 Subject: [PATCH] Add `kubectl` hooks for applying file(s) or kustomize (#1736) This enables you to write a `kubectl-apply` hook more declaratively than writing `command` and `args`: ``` releases: - name: myapp chart: mychart hooks: - events: ["presync"] kubectlApply: filename: path/to/manifests #kustomize: path/to/kustomize ``` --- pkg/event/bus.go | 34 ++++++++++++++++++---- pkg/event/bus_test.go | 68 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/pkg/event/bus.go b/pkg/event/bus.go index ffe63bc3..b52b815a 100644 --- a/pkg/event/bus.go +++ b/pkg/event/bus.go @@ -12,11 +12,12 @@ import ( ) type Hook struct { - Name string `yaml:"name"` - Events []string `yaml:"events"` - Command string `yaml:"command"` - Args []string `yaml:"args"` - ShowLogs bool `yaml:"showlogs"` + Name string `yaml:"name"` + Events []string `yaml:"events"` + Command string `yaml:"command"` + Kubectl map[string]string `yaml:"kubectlApply,omitempty"` + Args []string `yaml:"args"` + ShowLogs bool `yaml:"showlogs"` } type event struct { @@ -61,7 +62,28 @@ func (bus *Bus) Trigger(evt string, evtErr error, context map[string]interface{} name := hook.Name if name == "" { - name = hook.Command + if hook.Kubectl != nil { + name = "kubectlApply" + } else { + name = hook.Command + } + } + + if hook.Kubectl != nil { + if hook.Command != "" { + bus.Logger.Warnf("warn: ignoring command '%s' given within a kubectlApply hook", hook.Command) + } + hook.Command = "kubectl" + if val, found := hook.Kubectl["filename"]; found { + if _, found := hook.Kubectl["kustomize"]; found { + return false, fmt.Errorf("hook[%s]: kustomize & filename cannot be used together", name) + } + hook.Args = append([]string{"apply", "-f"}, val) + } else if val, found := hook.Kubectl["kustomize"]; found { + hook.Args = append([]string{"apply", "-k"}, val) + } else { + return false, fmt.Errorf("hook[%s]: either kustomize or filename must be given", name) + } } fmt.Fprintf(os.Stderr, "%s: basePath=%s\n", bus.StateFilePath, bus.BasePath) diff --git a/pkg/event/bus_test.go b/pkg/event/bus_test.go index 19e22473..42358607 100644 --- a/pkg/event/bus_test.go +++ b/pkg/event/bus_test.go @@ -43,21 +43,21 @@ func TestTrigger(t *testing.T) { }{ { "okhook1", - &Hook{"okhook1", []string{"foo"}, "ok", []string{}, true}, + &Hook{"okhook1", []string{"foo"}, "ok", nil, []string{}, true}, "foo", true, "", }, { "okhooké", - &Hook{"okhook2", []string{"foo"}, "ok", []string{}, false}, + &Hook{"okhook2", []string{"foo"}, "ok", nil, []string{}, false}, "foo", true, "", }, { "missinghook1", - &Hook{"okhook1", []string{"foo"}, "ok", []string{}, false}, + &Hook{"okhook1", []string{"foo"}, "ok", nil, []string{}, false}, "bar", false, "", @@ -71,18 +71,74 @@ func TestTrigger(t *testing.T) { }, { "nghook1", - &Hook{"nghook1", []string{"foo"}, "ng", []string{}, false}, + &Hook{"nghook1", []string{"foo"}, "ng", nil, []string{}, false}, "foo", false, "hook[nghook1]: command `ng` failed: cmd failed due to invalid cmd: ng", }, { "nghook2", - &Hook{"nghook2", []string{"foo"}, "ok", []string{"ng"}, false}, + &Hook{"nghook2", []string{"foo"}, "ok", nil, []string{"ng"}, false}, "foo", false, "hook[nghook2]: command `ok` failed: cmd failed due to invalid arg: ng", }, + { + "okkubeapply1", + &Hook{"okkubeapply1", []string{"foo"}, "", map[string]string{"kustomize": "kustodir"}, []string{}, false}, + "foo", + true, + "", + }, + { + "okkubeapply2", + &Hook{"okkubeapply2", []string{"foo"}, "", map[string]string{"filename": "resource.yaml"}, []string{}, false}, + "foo", + true, + "", + }, + { + "kokubeapply", + &Hook{"kokubeapply", []string{"foo"}, "", map[string]string{"kustomize": "kustodir", "filename": "resource.yaml"}, []string{}, true}, + "foo", + false, + "hook[kokubeapply]: kustomize & filename cannot be used together", + }, + { + "kokubeapply2", + &Hook{"kokubeapply2", []string{"foo"}, "", map[string]string{}, []string{}, true}, + "foo", + false, + "hook[kokubeapply2]: either kustomize or filename must be given", + }, + { + "kokubeapply3", + &Hook{"", []string{"foo"}, "", map[string]string{}, []string{}, true}, + "foo", + false, + "hook[kubectlApply]: either kustomize or filename must be given", + }, + { + "warnkubeapply1", + &Hook{"warnkubeapply1", []string{"foo"}, "ok", map[string]string{"filename": "resource.yaml"}, []string{}, true}, + "foo", + true, + "", + }, + { + "warnkubeapply2", + &Hook{"warnkubeapply2", []string{"foo"}, "", map[string]string{"filename": "resource.yaml"}, []string{"ng"}, true}, + "foo", + true, + "", + }, + { + "warnkubeapply3", + &Hook{"warnkubeapply3", []string{"foo"}, "ok", map[string]string{"filename": "resource.yaml"}, []string{"ng"}, true}, + "foo", + true, + "", + }, } readFile := func(filename string) ([]byte, error) { return nil, fmt.Errorf("unexpected call to readFile: %s", filename) @@ -117,7 +173,7 @@ func TestTrigger(t *testing.T) { if c.expectedErr != "" { if err == nil { - t.Error("error expected, but not occurred") + t.Errorf("error expected for case \"%s\", but not occurred", c.name) } else if err.Error() != c.expectedErr { t.Errorf("unexpected error for case \"%s\": expected=%s, actual=%v", c.name, c.expectedErr, err) }