mirror of
https://github.com/helmfile/helmfile.git
synced 2026-09-30 03:46:18 +02:00
fix: prevent panic in helmfile init on plugin install errors (#2401)
toCLIError() panics on unhandled error types (e.g. helmexec.ExitError from a failed helm plugin install). On Windows, plugin install hooks often fail due to missing 'sh', causing helmfile init to crash even when the plugin binary was placed correctly. - Add helmexec.ExitError case to toCLIError and replace panic in the default case with a graceful error return - After AddPlugin/UpdatePlugin errors, verify whether the plugin is actually present before failing; log a warning and continue if so Fixes #1983 Signed-off-by: Aditya Menon <amenon@canarytechnologies.com>
This commit is contained in:
+6
-1
@@ -1,6 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
@@ -23,6 +24,10 @@ var globalUsage = "Declaratively deploy your Kubernetes manifests, Kustomize con
|
||||
|
||||
func toCLIError(g *config.GlobalImpl, err error) error {
|
||||
if err != nil {
|
||||
var exitErr helmexec.ExitError
|
||||
if stderrors.As(err, &exitErr) {
|
||||
return errors.NewExitError(exitErr.Error(), exitErr.ExitStatus())
|
||||
}
|
||||
switch e := err.(type) {
|
||||
case *app.NoMatchingHelmfileError:
|
||||
noMatchingExitCode := 3
|
||||
@@ -35,7 +40,7 @@ func toCLIError(g *config.GlobalImpl, err error) error {
|
||||
case *app.Error:
|
||||
return errors.NewExitError(e.Error(), e.Code())
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: please file an github issue for this unhandled error: %T: %v", e, e))
|
||||
return errors.NewExitError(fmt.Sprintf("unexpected error: %T: %v", e, e), 1)
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/config"
|
||||
"github.com/helmfile/helmfile/pkg/errors"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
)
|
||||
|
||||
func TestToCLIError(t *testing.T) {
|
||||
g := config.NewGlobalImpl(&config.GlobalOptions{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
wantNil bool
|
||||
wantExitCode int
|
||||
wantMsgContains string
|
||||
}{
|
||||
{
|
||||
name: "nil error returns nil",
|
||||
err: nil,
|
||||
wantNil: true,
|
||||
},
|
||||
{
|
||||
name: "helmexec.ExitError returns correct exit code",
|
||||
err: helmexec.ExitError{
|
||||
Message: "helm command failed",
|
||||
Code: 7,
|
||||
},
|
||||
wantExitCode: 7,
|
||||
wantMsgContains: "helm command failed",
|
||||
},
|
||||
{
|
||||
name: "wrapped helmexec.ExitError preserves exit code",
|
||||
err: fmt.Errorf("helm version failed: %w", helmexec.ExitError{Message: "exit status 7", Code: 7}),
|
||||
wantExitCode: 7,
|
||||
wantMsgContains: "exit status 7",
|
||||
},
|
||||
{
|
||||
name: "unknown error type returns exit code 1 without panic",
|
||||
err: fmt.Errorf("some unexpected error"),
|
||||
wantExitCode: 1,
|
||||
wantMsgContains: "unexpected error",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Should never panic
|
||||
var result error
|
||||
assert.NotPanics(t, func() {
|
||||
result = toCLIError(g, tt.err)
|
||||
})
|
||||
|
||||
if tt.wantNil {
|
||||
assert.NoError(t, result)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Error(t, result)
|
||||
exitErr, ok := result.(*errors.ExitError)
|
||||
assert.True(t, ok, "expected *errors.ExitError, got %T", result)
|
||||
assert.Equal(t, tt.wantExitCode, exitErr.ExitCode())
|
||||
assert.Contains(t, exitErr.Error(), tt.wantMsgContains)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user