From bbde6c010f3661326bdd45efd0c12a2ad8642f04 Mon Sep 17 00:00:00 2001 From: "Md. Ishtiaq Islam" Date: Mon, 2 Dec 2024 15:53:30 +0600 Subject: [PATCH] Add support for templating for exec hook command Signed-off-by: Md. Ishtiaq Islam --- pkg/hooks/hook.go | 63 +++++++++++++++++++++++++++++++-------- pkg/hooks/hook_test.go | 67 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 104 insertions(+), 26 deletions(-) diff --git a/pkg/hooks/hook.go b/pkg/hooks/hook.go index ba6ca895e..c2d78471d 100644 --- a/pkg/hooks/hook.go +++ b/pkg/hooks/hook.go @@ -48,12 +48,49 @@ type HookExecutor struct { } func (e *HookExecutor) Execute() error { - if e.Hook.HTTPPost != nil && strings.Contains(e.Hook.HTTPPost.Body, "{{") { - if err := e.renderTemplate(); err != nil { + if err := e.renderHookTemplate(); err != nil { + return err + } + return probe.RunProbe(e.Config, e.Hook, e.ExecutorPod.Name, e.ExecutorPod.Namespace) +} + +func (e *HookExecutor) renderHookTemplate() error { + if e.Hook.Exec != nil { + if err := e.renderExecCommand(); err != nil { return err } } - return probe.RunProbe(e.Config, e.Hook, e.ExecutorPod.Name, e.ExecutorPod.Namespace) + + if e.Hook.HTTPPost != nil { + if err := e.renderHTTPPostBody(); err != nil { + return err + } + } + return nil +} + +func (e *HookExecutor) renderExecCommand() error { + for idx, cmd := range e.Hook.Exec.Command { + if strings.Contains(cmd, "{{") { + rendered, err := e.renderTemplate(cmd) + if err != nil { + return err + } + e.Hook.Exec.Command[idx] = rendered + } + } + return nil +} + +func (e *HookExecutor) renderHTTPPostBody() error { + if strings.Contains(e.Hook.HTTPPost.Body, "{{") { + rendered, err := e.renderTemplate(e.Hook.HTTPPost.Body) + if err != nil { + return err + } + e.Hook.HTTPPost.Body = rendered + } + return nil } var pool = sync.Pool{ @@ -62,24 +99,24 @@ var pool = sync.Pool{ }, } -func (e *HookExecutor) renderTemplate() error { - tpl, err := template.New("hook-template").Funcs(sprig.TxtFuncMap()).Parse(e.Hook.HTTPPost.Body) +func (e *HookExecutor) renderTemplate(text string) (string, error) { + tpl, err := template.New("hook-template"). + Funcs(sprig.TxtFuncMap()). + Option("missingkey=default"). + Parse(text) if err != nil { - return err + return "", err } - tpl.Option("missingkey=default") buf := pool.Get().(*bytes.Buffer) - buf.Reset() defer pool.Put(buf) + buf.Reset() - err = tpl.Execute(buf, e.Summary) - if err != nil { - return err + if err := tpl.Execute(buf, e.Summary); err != nil { + return "", err } - e.Hook.HTTPPost.Body = strings.TrimSpace(buf.String()) - return nil + return strings.TrimSpace(buf.String()), nil } type BackupHookExecutor struct { diff --git a/pkg/hooks/hook_test.go b/pkg/hooks/hook_test.go index 2abf9f6c3..c65dd067f 100644 --- a/pkg/hooks/hook_test.go +++ b/pkg/hooks/hook_test.go @@ -49,34 +49,34 @@ func TestHookExecutor_renderTemplate(t *testing.T) { expectedBody string }{ { - name: "Successful session", + name: "[HTTPPost] Successful session", fields: fields{ - Hook: defaultHookTemplate(), + Hook: defaultHookTemplate(false), Summary: defaultSummary(), }, wantErr: false, expectedBody: "Name: test-session Namespace: test Phase: Succeeded", }, { - name: "Failed session", + name: "[HTTPPost] Failed session", fields: fields{ - Hook: defaultHookTemplate(), + Hook: defaultHookTemplate(false), Summary: failedSummary(), }, wantErr: false, expectedBody: "Name: test-session Namespace: test Phase: Failed", }, { - name: "Failed session with escape character in error message", + name: "[HTTPPost] Failed session with escape character in error message", fields: fields{ - Hook: defaultHookTemplate(), + Hook: defaultHookTemplate(false), Summary: failedSummary(), }, wantErr: false, expectedBody: "Name: test-session Namespace: test Phase: Failed", }, { - name: "Conditional hook with Succeeded phase", + name: "[HTTPPost] Conditional hook with Succeeded phase", fields: fields{ Hook: conditionalHookTemplate(defaultErrorMsg), Summary: defaultSummary(), @@ -85,7 +85,7 @@ func TestHookExecutor_renderTemplate(t *testing.T) { expectedBody: "Succeeded", }, { - name: "Conditional hook with Failed phase", + name: "[HTTPPost] Conditional hook with Failed phase", fields: fields{ Hook: conditionalHookTemplate(defaultErrorMsg), Summary: failedSummary(), @@ -94,7 +94,7 @@ func TestHookExecutor_renderTemplate(t *testing.T) { expectedBody: fmt.Sprintf("Failed. Reason: %s", defaultErrorMsg), }, { - name: "Conditional hook with escape character in error message", + name: "[HTTPPost] Conditional hook with escape character in error message", fields: fields{ Hook: conditionalHookTemplate(errorMsgWithEscapeCharacter), Summary: failedSummary(), @@ -102,6 +102,24 @@ func TestHookExecutor_renderTemplate(t *testing.T) { wantErr: false, expectedBody: fmt.Sprintf("Failed. Reason: %s", errorMsgWithEscapeCharacter), }, + { + name: "[Exec] Successful session", + fields: fields{ + Hook: defaultHookTemplate(true), + Summary: defaultSummary(), + }, + wantErr: false, + expectedBody: "{\"text\":\":x: Name: test-session Namespace: test Phase: Succeeded\"}", + }, + { + name: "[Exec] Failed session", + fields: fields{ + Hook: defaultHookTemplate(true), + Summary: failedSummary(), + }, + wantErr: false, + expectedBody: "{\"text\":\":x: Name: test-session Namespace: test Phase: Failed\"}", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -111,14 +129,21 @@ func TestHookExecutor_renderTemplate(t *testing.T) { ExecutorPod: tt.fields.ExecutorPod, Summary: tt.fields.Summary, } - if err := e.renderTemplate(); (err != nil) != tt.wantErr { - t.Errorf("renderTemplate() error = %v, wantErr %v", err, tt.wantErr) + if err := e.renderHookTemplate(); (err != nil) != tt.wantErr { + t.Errorf("renderHookTemplate() error = %v, wantErr %v", err, tt.wantErr) return } - if tt.expectedBody != e.Hook.HTTPPost.Body { + if e.Hook.HTTPPost != nil && + tt.expectedBody != e.Hook.HTTPPost.Body { t.Errorf("Expected: %v, found: %v", tt.expectedBody, e.Hook.HTTPPost.Body) return } + if e.Hook.Exec != nil { + if e.Hook.Exec.Command[6] != tt.expectedBody { + t.Errorf("Expected: %v, found: %v", tt.expectedBody, e.Hook.Exec.Command[6]) + return + } + } }) } } @@ -159,7 +184,23 @@ func failedSummary() *v1beta1.Summary { }) } -func defaultHookTemplate() *prober.Handler { +func defaultHookTemplate(isExec bool) *prober.Handler { + if isExec { + return &prober.Handler{ + Exec: &core.ExecAction{ + Command: []string{ + "curl", + "-X", + "POST", + "-H", + "Content-Type: application/json", + "-d", + `{"text":":x: Name: {{ .Name }} Namespace: {{.Namespace}} Phase: {{.Status.Phase}}"}`, + "https://slack-webhook.url/stash", + }, + }, + } + } return &prober.Handler{ HTTPPost: &prober.HTTPPostAction{ Body: "Name: {{ .Name }} Namespace: {{.Namespace}} Phase: {{.Status.Phase}}",