diff --git a/cmd/workspace/clusters/overrides.go b/cmd/workspace/clusters/overrides.go index 45c530a14a2..c73b442a1a7 100644 --- a/cmd/workspace/clusters/overrides.go +++ b/cmd/workspace/clusters/overrides.go @@ -1,9 +1,11 @@ package clusters import ( + "errors" "strings" "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/spf13/cobra" ) @@ -93,8 +95,26 @@ func sparkVersionsOverride(sparkVersionsCmd *cobra.Command) { `) } +// startIdempotentOverride matches the generated help text: starting a cluster +// that is not TERMINATED is a no-op. The Clusters API returns INVALID_STATE +// (ErrInvalidState) for that case, which made scripts fail despite the docs. +func startIdempotentOverride(startCmd *cobra.Command, _ *compute.StartCluster) { + originalRunE := startCmd.RunE + startCmd.RunE = func(cmd *cobra.Command, args []string) error { + err := originalRunE(cmd, args) + if err == nil { + return nil + } + if errors.Is(err, apierr.ErrInvalidState) { + return nil + } + return err + } +} + func init() { listOverrides = append(listOverrides, listOverride) listNodeTypesOverrides = append(listNodeTypesOverrides, listNodeTypesOverride) sparkVersionsOverrides = append(sparkVersionsOverrides, sparkVersionsOverride) + startOverrides = append(startOverrides, startIdempotentOverride) } diff --git a/cmd/workspace/clusters/overrides_test.go b/cmd/workspace/clusters/overrides_test.go new file mode 100644 index 00000000000..f4cd221eb86 --- /dev/null +++ b/cmd/workspace/clusters/overrides_test.go @@ -0,0 +1,60 @@ +package clusters + +import ( + "errors" + "testing" + + "github.com/databricks/databricks-sdk-go/apierr" + "github.com/databricks/databricks-sdk-go/service/compute" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStartIdempotentOverride(t *testing.T) { + t.Run("swallows INVALID_STATE from a non terminated cluster", func(t *testing.T) { + cmd := &cobra.Command{} + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return apierr.ErrInvalidState + } + + startIdempotentOverride(cmd, &compute.StartCluster{}) + require.NoError(t, cmd.RunE(cmd, nil)) + }) + + t.Run("swallows APIError with INVALID_STATE code", func(t *testing.T) { + cmd := &cobra.Command{} + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return &apierr.APIError{ + ErrorCode: "INVALID_STATE", + Message: "Cluster abc is in unexpected state Running.", + } + } + + startIdempotentOverride(cmd, &compute.StartCluster{}) + require.NoError(t, cmd.RunE(cmd, nil)) + }) + + t.Run("preserves other errors", func(t *testing.T) { + cmd := &cobra.Command{} + want := errors.New("boom") + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return want + } + + startIdempotentOverride(cmd, &compute.StartCluster{}) + err := cmd.RunE(cmd, nil) + require.Error(t, err) + assert.ErrorIs(t, err, want) + }) + + t.Run("passes success through", func(t *testing.T) { + cmd := &cobra.Command{} + cmd.RunE = func(cmd *cobra.Command, args []string) error { + return nil + } + + startIdempotentOverride(cmd, &compute.StartCluster{}) + require.NoError(t, cmd.RunE(cmd, nil)) + }) +}