fix: cleanup hooks not receiving error signal
Closes #1041 Signed-off-by: yxxhero <11087727+yxxhero@users.noreply.github.com> Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
parent
da8a1ff2e6
commit
c800910903
|
|
@ -0,0 +1,124 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/helmfile/vals"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/exectest"
|
||||
ffs "github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
)
|
||||
|
||||
func TestCleanupHooksErrorPropagation(t *testing.T) {
|
||||
type testcase struct {
|
||||
name string
|
||||
files map[string]string
|
||||
releaseName string
|
||||
expectedError bool
|
||||
expectedInLogs string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
bs := runWithLogCapture(t, "info", func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, tc.files)
|
||||
|
||||
syncErr := app.Sync(applyConfig{
|
||||
concurrency: 1,
|
||||
logger: logger,
|
||||
})
|
||||
|
||||
if tc.expectedError {
|
||||
assert.Error(t, syncErr, "expected error for release %s", tc.releaseName)
|
||||
} else {
|
||||
assert.NoError(t, syncErr, "unexpected error for release %s", tc.releaseName)
|
||||
}
|
||||
})
|
||||
|
||||
logOutput := bs.String()
|
||||
assert.Contains(t, logOutput, tc.expectedInLogs, "unexpected log output")
|
||||
}
|
||||
|
||||
t.Run("cleanup hook receives error when sync fails", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
name: "sync-failure-cleanup-error",
|
||||
releaseName: "error-release",
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
hooks:
|
||||
- name: global-cleanup
|
||||
events:
|
||||
- cleanup
|
||||
showlogs: true
|
||||
command: echo
|
||||
args:
|
||||
- "error is '{{ .Event.Error }}'"
|
||||
|
||||
releases:
|
||||
- name: error-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
`,
|
||||
},
|
||||
expectedError: true,
|
||||
expectedInLogs: "error is 'failed processing release error-release: error'",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("cleanup hook receives nil when sync succeeds", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
name: "sync-success-cleanup-nil",
|
||||
releaseName: "success-release",
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
hooks:
|
||||
- name: global-cleanup
|
||||
events:
|
||||
- cleanup
|
||||
showlogs: true
|
||||
command: echo
|
||||
args:
|
||||
- "error is '{{ .Event.Error }}'"
|
||||
|
||||
releases:
|
||||
- name: success-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
`,
|
||||
},
|
||||
expectedError: false,
|
||||
expectedInLogs: "error is '<nil>'",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest/observer"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/environment"
|
||||
"github.com/helmfile/helmfile/pkg/event"
|
||||
ffs "github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
)
|
||||
|
||||
type mockRunner struct {
|
||||
executeCalls []struct {
|
||||
cmd string
|
||||
args []string
|
||||
env map[string]string
|
||||
}
|
||||
}
|
||||
|
||||
func (r *mockRunner) Execute(cmd string, args []string, env map[string]string, _ bool) ([]byte, error) {
|
||||
r.executeCalls = append(r.executeCalls, struct {
|
||||
cmd string
|
||||
args []string
|
||||
env map[string]string
|
||||
}{cmd: cmd, args: args, env: env})
|
||||
return []byte(""), nil
|
||||
}
|
||||
|
||||
func (r *mockRunner) ExecuteStdIn(cmd string, args []string, env map[string]string, _ io.Reader) ([]byte, error) {
|
||||
return []byte(""), nil
|
||||
}
|
||||
|
||||
func TestTriggerGlobalCleanupEventWithMockRunner(t *testing.T) {
|
||||
runner := &mockRunner{}
|
||||
|
||||
core, _ := observer.New(zap.InfoLevel)
|
||||
logger := zap.New(core).Sugar()
|
||||
|
||||
testError := errors.New("sync failed: release error")
|
||||
|
||||
hooks := []event.Hook{
|
||||
{
|
||||
Name: "cleanup-with-error",
|
||||
Events: []string{"cleanup"},
|
||||
Command: "echo",
|
||||
Args: []string{"error is '{{ .Event.Error }}'"},
|
||||
ShowLogs: true,
|
||||
},
|
||||
}
|
||||
|
||||
bus := &event.Bus{
|
||||
Hooks: hooks,
|
||||
StateFilePath: "/path/to/helmfile.yaml",
|
||||
BasePath: ".",
|
||||
Namespace: "default",
|
||||
Env: environment.Environment{Name: "default"},
|
||||
Logger: logger,
|
||||
Fs: ffs.DefaultFileSystem(),
|
||||
Runner: runner,
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"HelmfileCommand": "sync",
|
||||
}
|
||||
|
||||
executed, err := bus.Trigger("cleanup", testError, data)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !executed {
|
||||
t.Fatal("expected cleanup hook to be executed")
|
||||
}
|
||||
|
||||
if len(runner.executeCalls) != 1 {
|
||||
t.Fatalf("expected 1 execute call, got %d", len(runner.executeCalls))
|
||||
}
|
||||
|
||||
call := runner.executeCalls[0]
|
||||
if call.cmd != "echo" {
|
||||
t.Errorf("expected command 'echo', got %q", call.cmd)
|
||||
}
|
||||
|
||||
if len(call.args) != 1 {
|
||||
t.Fatalf("expected 1 arg, got %d", len(call.args))
|
||||
}
|
||||
|
||||
expectedArg := "error is 'sync failed: release error'"
|
||||
if !strings.Contains(call.args[0], "error is") {
|
||||
t.Errorf("expected arg to contain 'error is', got %q", call.args[0])
|
||||
}
|
||||
|
||||
if call.args[0] != expectedArg {
|
||||
t.Errorf("expected arg %q, got %q", expectedArg, call.args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestTriggerGlobalCleanupEventNilError(t *testing.T) {
|
||||
runner := &mockRunner{}
|
||||
|
||||
core, _ := observer.New(zap.InfoLevel)
|
||||
logger := zap.New(core).Sugar()
|
||||
|
||||
hooks := []event.Hook{
|
||||
{
|
||||
Name: "cleanup-nil-error",
|
||||
Events: []string{"cleanup"},
|
||||
Command: "echo",
|
||||
Args: []string{"error is '{{ .Event.Error }}'"},
|
||||
ShowLogs: true,
|
||||
},
|
||||
}
|
||||
|
||||
bus := &event.Bus{
|
||||
Hooks: hooks,
|
||||
StateFilePath: "/path/to/helmfile.yaml",
|
||||
BasePath: ".",
|
||||
Namespace: "default",
|
||||
Env: environment.Environment{Name: "default"},
|
||||
Logger: logger,
|
||||
Fs: ffs.DefaultFileSystem(),
|
||||
Runner: runner,
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"HelmfileCommand": "sync",
|
||||
}
|
||||
|
||||
executed, err := bus.Trigger("cleanup", nil, data)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !executed {
|
||||
t.Fatal("expected cleanup hook to be executed")
|
||||
}
|
||||
|
||||
if len(runner.executeCalls) != 1 {
|
||||
t.Fatalf("expected 1 execute call, got %d", len(runner.executeCalls))
|
||||
}
|
||||
|
||||
call := runner.executeCalls[0]
|
||||
expectedArg := "error is '<nil>'"
|
||||
if call.args[0] != expectedArg {
|
||||
t.Errorf("expected arg %q, got %q", expectedArg, call.args[0])
|
||||
}
|
||||
}
|
||||
|
||||
var _ helmexec.Runner = &mockRunner{}
|
||||
Loading…
Reference in New Issue