diff --git a/cmd/collect.go b/cmd/collect.go index e6dc6b0f..2576e764 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -17,7 +17,6 @@ import ( "github.com/turbot/pipe-fittings/v2/cmdconfig" pconstants "github.com/turbot/pipe-fittings/v2/constants" "github.com/turbot/pipe-fittings/v2/contexthelpers" - "github.com/turbot/pipe-fittings/v2/error_helpers" "github.com/turbot/pipe-fittings/v2/modconfig" "github.com/turbot/pipe-fittings/v2/parse" localcmdconfig "github.com/turbot/tailpipe/internal/cmdconfig" @@ -25,6 +24,7 @@ import ( "github.com/turbot/tailpipe/internal/config" "github.com/turbot/tailpipe/internal/constants" "github.com/turbot/tailpipe/internal/database" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" "github.com/turbot/tailpipe/internal/plugin" "golang.org/x/exp/maps" ) @@ -71,8 +71,8 @@ func runCollectCmd(cmd *cobra.Command, args []string) { } if err != nil { - if errors.Is(err, context.Canceled) { - fmt.Println("Collection cancelled.") //nolint:forbidigo // ui output + if error_helpers.IsCancelledError(err) { + fmt.Println("tailpipe collect command cancelled.") //nolint:forbidigo // ui output } else { error_helpers.ShowError(ctx, err) } @@ -364,14 +364,13 @@ func setExitCodeForCollectError(err error) { if exitCode != 0 || err == nil { return } - // TODO Set exit code for cancellation - if errors.Is(err, context.Canceled) { - exitCode = 0 + // set exit code for cancellation + if error_helpers.IsCancelledError(err) { + exitCode = pconstants.ExitCodeOperationCancelled return } - // TODO #errors - assign exit codes https://github.com/turbot/tailpipe/issues/496 - exitCode = 1 + exitCode = pconstants.ExitCodeCollectionFailed } // parse the from time diff --git a/cmd/compact.go b/cmd/compact.go index a223f936..c777b203 100644 --- a/cmd/compact.go +++ b/cmd/compact.go @@ -2,7 +2,6 @@ package cmd import ( "context" - "errors" "fmt" "log/slog" "os" @@ -15,11 +14,11 @@ import ( "github.com/turbot/pipe-fittings/v2/cmdconfig" pconstants "github.com/turbot/pipe-fittings/v2/constants" "github.com/turbot/pipe-fittings/v2/contexthelpers" - "github.com/turbot/pipe-fittings/v2/error_helpers" localcmdconfig "github.com/turbot/tailpipe/internal/cmdconfig" "github.com/turbot/tailpipe/internal/config" "github.com/turbot/tailpipe/internal/constants" "github.com/turbot/tailpipe/internal/database" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" "golang.org/x/exp/maps" ) @@ -49,9 +48,9 @@ func runCompactCmd(cmd *cobra.Command, args []string) { if err != nil { setExitCodeForCompactError(err) - if errors.Is(err, context.Canceled) { + if error_helpers.IsCancelledError(err) { //nolint:forbidigo // ui - fmt.Println("Compact cancelled") + fmt.Println("tailpipe compact command cancelled.") } else { error_helpers.ShowError(ctx, err) } @@ -126,5 +125,11 @@ func setExitCodeForCompactError(err error) { if exitCode != 0 || err == nil { return } - exitCode = 1 + // set exit code for cancellation + if error_helpers.IsCancelledError(err) { + exitCode = pconstants.ExitCodeOperationCancelled + return + } + + exitCode = pconstants.ExitCodeCompactFailed } diff --git a/cmd/connect.go b/cmd/connect.go index c0e8d444..a7b4da56 100644 --- a/cmd/connect.go +++ b/cmd/connect.go @@ -4,13 +4,14 @@ import ( "context" "encoding/json" "fmt" - "golang.org/x/exp/maps" "log" "os" "path/filepath" "strings" "time" + "golang.org/x/exp/maps" + "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/thediveo/enumflag/v2" @@ -18,13 +19,14 @@ import ( "github.com/turbot/pipe-fittings/v2/cmdconfig" "github.com/turbot/pipe-fittings/v2/connection" pconstants "github.com/turbot/pipe-fittings/v2/constants" - "github.com/turbot/pipe-fittings/v2/error_helpers" + "github.com/turbot/pipe-fittings/v2/contexthelpers" pfilepaths "github.com/turbot/pipe-fittings/v2/filepaths" "github.com/turbot/pipe-fittings/v2/parse" localcmdconfig "github.com/turbot/tailpipe/internal/cmdconfig" "github.com/turbot/tailpipe/internal/config" "github.com/turbot/tailpipe/internal/constants" "github.com/turbot/tailpipe/internal/database" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" ) // variable used to assign the output mode flag @@ -94,13 +96,21 @@ The generated script can be used with DuckDB: func runConnectCmd(cmd *cobra.Command, _ []string) { var err error var initFilePath string - ctx := cmd.Context() + ctx, cancel := context.WithCancel(cmd.Context()) + contexthelpers.StartCancelHandler(cancel) defer func() { if r := recover(); r != nil { err = helpers.ToError(r) } - setExitCodeForConnectError(err) + if err != nil { + if error_helpers.IsCancelledError(err) { + fmt.Println("tailpipe connect command cancelled.") //nolint:forbidigo // ui output + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForConnectError(err) + } displayOutput(ctx, initFilePath, err) }() @@ -409,8 +419,11 @@ func setExitCodeForConnectError(err error) { if exitCode != 0 || err == nil || viper.GetString(pconstants.ArgOutput) == pconstants.OutputFormatJSON { return } - - exitCode = 1 + if error_helpers.IsCancelledError(err) { + exitCode = pconstants.ExitCodeOperationCancelled + return + } + exitCode = pconstants.ExitCodeConnectFailed } // generateInitFilename generates a temporary filename with a timestamp diff --git a/cmd/format.go b/cmd/format.go index 97543dd2..5f5d40de 100644 --- a/cmd/format.go +++ b/cmd/format.go @@ -12,12 +12,12 @@ import ( "github.com/turbot/pipe-fittings/v2/cmdconfig" pconstants "github.com/turbot/pipe-fittings/v2/constants" "github.com/turbot/pipe-fittings/v2/contexthelpers" - "github.com/turbot/pipe-fittings/v2/error_helpers" "github.com/turbot/pipe-fittings/v2/printers" "github.com/turbot/pipe-fittings/v2/utils" localcmdconfig "github.com/turbot/tailpipe/internal/cmdconfig" "github.com/turbot/tailpipe/internal/constants" "github.com/turbot/tailpipe/internal/display" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" ) // variable used to assign the output mode flag @@ -73,11 +73,20 @@ func runFormatListCmd(cmd *cobra.Command, args []string) { ctx, cancel := context.WithCancel(cmd.Context()) contexthelpers.StartCancelHandler(cancel) utils.LogTime("runFormatListCmd start") + var err error defer func() { utils.LogTime("runFormatListCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("tailpipe format list command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForFormatError(err,1) } }() @@ -99,8 +108,8 @@ func runFormatListCmd(cmd *cobra.Command, args []string) { // Print err = printer.PrintResource(ctx, printableResource, cmd.OutOrStdout()) if err != nil { - error_helpers.ShowError(ctx, err) - exitCode = pconstants.ExitCodeUnknownErrorPanic + exitCode = pconstants.ExitCodeOutputRenderingFailed + return } } @@ -128,11 +137,20 @@ func runFormatShowCmd(cmd *cobra.Command, args []string) { ctx, cancel := context.WithCancel(cmd.Context()) contexthelpers.StartCancelHandler(cancel) utils.LogTime("runFormatShowCmd start") + var err error defer func() { utils.LogTime("runFormatShowCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("tailpipe format show command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForFormatError(err, 1) } }() @@ -149,7 +167,21 @@ func runFormatShowCmd(cmd *cobra.Command, args []string) { // Print err = printer.PrintResource(ctx, printableResource, cmd.OutOrStdout()) if err != nil { - error_helpers.ShowError(ctx, err) - exitCode = pconstants.ExitCodeUnknownErrorPanic + exitCode = pconstants.ExitCodeOutputRenderingFailed + return + } +} + +func setExitCodeForFormatError(err error, nonCancelCode int) { + // set exit code only if an error occurred and no exit code is already set + if exitCode != 0 || err == nil { + return + } + // set exit code for cancellation + if error_helpers.IsCancelledError(err) { + exitCode = pconstants.ExitCodeOperationCancelled + return } + // no dedicated format exit code exists yet; use generic nonzero failure + exitCode = nonCancelCode } diff --git a/cmd/partition.go b/cmd/partition.go index 2a25ee2c..78029017 100644 --- a/cmd/partition.go +++ b/cmd/partition.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "errors" "fmt" "log/slog" "os" @@ -15,7 +16,6 @@ import ( "github.com/turbot/pipe-fittings/v2/cmdconfig" pconstants "github.com/turbot/pipe-fittings/v2/constants" "github.com/turbot/pipe-fittings/v2/contexthelpers" - "github.com/turbot/pipe-fittings/v2/error_helpers" "github.com/turbot/pipe-fittings/v2/printers" "github.com/turbot/pipe-fittings/v2/statushooks" "github.com/turbot/pipe-fittings/v2/utils" @@ -24,6 +24,7 @@ import ( "github.com/turbot/tailpipe/internal/constants" "github.com/turbot/tailpipe/internal/database" "github.com/turbot/tailpipe/internal/display" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" "github.com/turbot/tailpipe/internal/filepaths" "github.com/turbot/tailpipe/internal/plugin" ) @@ -79,11 +80,20 @@ func runPartitionListCmd(cmd *cobra.Command, args []string) { ctx, cancel := context.WithCancel(cmd.Context()) contexthelpers.StartCancelHandler(cancel) utils.LogTime("runPartitionListCmd start") + var err error defer func() { utils.LogTime("runPartitionListCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("taillpipe partition list command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForPartitionError(err) } }() @@ -110,8 +120,8 @@ func runPartitionListCmd(cmd *cobra.Command, args []string) { // Print err = printer.PrintResource(ctx, printableResource, cmd.OutOrStdout()) if err != nil { - error_helpers.ShowError(ctx, err) - exitCode = pconstants.ExitCodeUnknownErrorPanic + exitCode = pconstants.ExitCodeOutputRenderingFailed + return } } @@ -138,13 +148,23 @@ func partitionShowCmd() *cobra.Command { func runPartitionShowCmd(cmd *cobra.Command, args []string) { // setup a cancel context and start cancel handler ctx, cancel := context.WithCancel(cmd.Context()) + //TODO: https://github.com/turbot/tailpipe/issues/563 none of the functions called in this command will return a cancellation error. Cancellation won't work right now contexthelpers.StartCancelHandler(cancel) utils.LogTime("runPartitionShowCmd start") + var err error defer func() { utils.LogTime("runPartitionShowCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("tailpipe partition show command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForPartitionError(err) } }() @@ -183,8 +203,8 @@ func runPartitionShowCmd(cmd *cobra.Command, args []string) { // Print err = printer.PrintResource(ctx, printableResource, cmd.OutOrStdout()) if err != nil { - error_helpers.ShowError(ctx, err) - exitCode = pconstants.ExitCodeUnknownErrorPanic + exitCode = pconstants.ExitCodeOutputRenderingFailed + return } } @@ -213,12 +233,23 @@ func partitionDeleteCmd() *cobra.Command { } func runPartitionDeleteCmd(cmd *cobra.Command, args []string) { - ctx := cmd.Context() - + // setup a cancel context and start cancel handler + ctx, cancel := context.WithCancel(cmd.Context()) + //TODO: https://github.com/turbot/tailpipe/issues/563 none of the functions called in this command will return a cancellation error. Cancellation won't work right now + contexthelpers.StartCancelHandler(cancel) + var err error defer func() { if r := recover(); r != nil { - exitCode = pconstants.ExitCodeUnknownErrorPanic - error_helpers.FailOnError(helpers.ToError(r)) + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("Partition cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForPartitionError(err) } }() @@ -279,7 +310,14 @@ func runPartitionDeleteCmd(cmd *cobra.Command, args []string) { spinner.Show() rowsDeleted, err := database.DeletePartition(ctx, partition, fromTime, toTime, db) spinner.Hide() - error_helpers.FailOnError(err) + if err != nil { + if errors.Is(err, context.Canceled) { + exitCode = pconstants.ExitCodeOperationCancelled + } else { + exitCode = 1 + } + error_helpers.FailOnError(err) + } // build the collection state path collectionStatePath := partition.CollectionStatePath(config.GlobalWorkspaceProfile.GetCollectionDir()) @@ -288,6 +326,7 @@ func runPartitionDeleteCmd(cmd *cobra.Command, args []string) { if fromTime.IsZero() { err := os.Remove(collectionStatePath) if err != nil && !os.IsNotExist(err) { + exitCode = 1 error_helpers.FailOnError(fmt.Errorf("failed to delete collection state file: %s", err.Error())) } } else { @@ -296,7 +335,14 @@ func runPartitionDeleteCmd(cmd *cobra.Command, args []string) { pluginManager := plugin.NewPluginManager() defer pluginManager.Close() err = pluginManager.UpdateCollectionState(ctx, partition, fromTime, collectionStatePath) - error_helpers.FailOnError(err) + if err != nil { + if errors.Is(err, context.Canceled) { + exitCode = pconstants.ExitCodeOperationCancelled + } else { + exitCode = 1 + } + error_helpers.FailOnError(err) + } } // now prune the collection folders @@ -317,3 +363,15 @@ func buildStatusMessage(rowsDeleted int, partition string, fromStr string) inter return fmt.Sprintf("\nDeleted partition '%s'%s%s.\n", partition, fromStr, deletedStr) } + +func setExitCodeForPartitionError(err error) { + if exitCode != 0 || err == nil { + return + } + if error_helpers.IsCancelledError(err) { + exitCode = pconstants.ExitCodeOperationCancelled + return + } + // no dedicated partition exit code; use generic nonzero failure + exitCode = 1 +} diff --git a/cmd/plugin.go b/cmd/plugin.go index bd1475b4..ba7dc795 100644 --- a/cmd/plugin.go +++ b/cmd/plugin.go @@ -16,7 +16,6 @@ import ( "github.com/turbot/pipe-fittings/v2/cmdconfig" pconstants "github.com/turbot/pipe-fittings/v2/constants" "github.com/turbot/pipe-fittings/v2/contexthelpers" - "github.com/turbot/pipe-fittings/v2/error_helpers" "github.com/turbot/pipe-fittings/v2/filepaths" "github.com/turbot/pipe-fittings/v2/installationstate" pociinstaller "github.com/turbot/pipe-fittings/v2/ociinstaller" @@ -29,6 +28,7 @@ import ( "github.com/turbot/tailpipe/internal/config" "github.com/turbot/tailpipe/internal/constants" "github.com/turbot/tailpipe/internal/display" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" "github.com/turbot/tailpipe/internal/ociinstaller" "github.com/turbot/tailpipe/internal/plugin" ) @@ -238,13 +238,25 @@ var pluginInstallSteps = []string{ } func runPluginInstallCmd(cmd *cobra.Command, args []string) { - ctx := cmd.Context() + //setup a cancel context and start cancel handler + ctx, cancel := context.WithCancel(cmd.Context()) + //TODO: https://github.com/turbot/tailpipe/issues/563 none of the functions called in this command will return a cancellation error. Cancellation won't work right now + contexthelpers.StartCancelHandler(cancel) utils.LogTime("runPluginInstallCmd install") + var err error defer func() { utils.LogTime("runPluginInstallCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("tailpipe plugin install command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForPluginError(err, 1) } }() @@ -374,13 +386,25 @@ func doPluginInstall(ctx context.Context, bar *uiprogress.Bar, pluginName string } func runPluginUpdateCmd(cmd *cobra.Command, args []string) { - ctx := cmd.Context() + //setup a cancel context and start cancel handler + ctx, cancel := context.WithCancel(cmd.Context()) + //TODO: https://github.com/turbot/tailpipe/issues/563 none of the functions called in this command will return a cancellation error. Cancellation won't work right now + contexthelpers.StartCancelHandler(cancel) utils.LogTime("runPluginUpdateCmd start") + var err error defer func() { utils.LogTime("runPluginUpdateCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("tailpipe plugin update command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForPluginError(err, 1) } }() @@ -645,15 +669,24 @@ func installPlugin(ctx context.Context, resolvedPlugin pplugin.ResolvedPluginVer func runPluginUninstallCmd(cmd *cobra.Command, args []string) { // setup a cancel context and start cancel handler ctx, cancel := context.WithCancel(cmd.Context()) + //TODO: https://github.com/turbot/tailpipe/issues/563 none of the functions called in this command will return a cancellation error. Cancellation won't work right now contexthelpers.StartCancelHandler(cancel) utils.LogTime("runPluginUninstallCmd uninstall") - + var err error defer func() { utils.LogTime("runPluginUninstallCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("tailpipe plugin uninstall command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForPluginError(err, 1) } }() @@ -701,6 +734,8 @@ func runPluginUninstallCmd(cmd *cobra.Command, args []string) { continue } } + } else if error_helpers.IsCancelledError(err) { + exitCode = pconstants.ExitCodeOperationCancelled } error_helpers.ShowErrorWithMessage(ctx, err, fmt.Sprintf("Failed to uninstall plugin '%s'", p)) } else { @@ -742,11 +777,20 @@ func runPluginListCmd(cmd *cobra.Command, _ []string) { // Clean up plugin temporary directories from previous crashes/interrupted installations filepaths.CleanupPluginTempDirs() + var err error defer func() { utils.LogTime("runPluginListCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("tailpipe plugin list command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForPluginError(err, pconstants.ExitCodePluginListFailure) } }() @@ -772,8 +816,8 @@ func runPluginListCmd(cmd *cobra.Command, _ []string) { // Print err = printer.PrintResource(ctx, printableResource, cmd.OutOrStdout()) if err != nil { - error_helpers.ShowError(ctx, err) - exitCode = pconstants.ExitCodePluginListFailure + exitCode = pconstants.ExitCodeOutputRenderingFailed + return } } @@ -787,6 +831,7 @@ func runPluginShowCmd(cmd *cobra.Command, args []string) { //setup a cancel context and start cancel handler ctx, cancel := context.WithCancel(cmd.Context()) + //TODO: https://github.com/turbot/tailpipe/issues/563 none of the functions called in this command will return a cancellation error. Cancellation won't work right now contexthelpers.StartCancelHandler(cancel) utils.LogTime("runPluginShowCmd start") @@ -794,11 +839,20 @@ func runPluginShowCmd(cmd *cobra.Command, args []string) { // Clean up plugin temporary directories from previous crashes/interrupted installations filepaths.CleanupPluginTempDirs() + var err error defer func() { utils.LogTime("runPluginShowCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("tailpipe plugin show command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForPluginError(err, pconstants.ExitCodePluginShowFailure) } }() @@ -820,7 +874,18 @@ func runPluginShowCmd(cmd *cobra.Command, args []string) { // Print err = printer.PrintResource(ctx, printableResource, cmd.OutOrStdout()) if err != nil { - error_helpers.ShowError(ctx, err) - exitCode = pconstants.ExitCodePluginListFailure + exitCode = pconstants.ExitCodeOutputRenderingFailed + return + } +} + +func setExitCodeForPluginError(err error, nonCancelCode int) { + if exitCode != 0 || err == nil { + return + } + if error_helpers.IsCancelledError(err) { + exitCode = pconstants.ExitCodeOperationCancelled + return } + exitCode = nonCancelCode } diff --git a/cmd/query.go b/cmd/query.go index 08bec108..cdc6e011 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -73,7 +73,7 @@ func runQueryCmd(cmd *cobra.Command, args []string) { } if err != nil { error_helpers.ShowError(ctx, err) - setExitCodeForQueryError(err) + exitCode = pconstants.ExitCodeInitializationFailed } }() @@ -112,12 +112,3 @@ func runQueryCmd(cmd *cobra.Command, args []string) { exitCode = pconstants.ExitCodeQueryExecutionFailed } } - -func setExitCodeForQueryError(err error) { - // if exit code already set, leave as is - if exitCode != 0 || err == nil { - return - } - - exitCode = 1 -} diff --git a/cmd/source.go b/cmd/source.go index 972bfc97..ecc2785a 100644 --- a/cmd/source.go +++ b/cmd/source.go @@ -12,12 +12,12 @@ import ( "github.com/turbot/pipe-fittings/v2/cmdconfig" pconstants "github.com/turbot/pipe-fittings/v2/constants" "github.com/turbot/pipe-fittings/v2/contexthelpers" - "github.com/turbot/pipe-fittings/v2/error_helpers" "github.com/turbot/pipe-fittings/v2/printers" "github.com/turbot/pipe-fittings/v2/utils" localcmdconfig "github.com/turbot/tailpipe/internal/cmdconfig" "github.com/turbot/tailpipe/internal/constants" "github.com/turbot/tailpipe/internal/display" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" ) func sourceCmd() *cobra.Command { @@ -70,11 +70,20 @@ func runSourceListCmd(cmd *cobra.Command, args []string) { ctx, cancel := context.WithCancel(cmd.Context()) contexthelpers.StartCancelHandler(cancel) utils.LogTime("runSourceListCmd start") + var err error defer func() { utils.LogTime("runSourceListCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("tailpipe source list command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForSourceError(err) } }() @@ -96,8 +105,8 @@ func runSourceListCmd(cmd *cobra.Command, args []string) { // Print err = printer.PrintResource(ctx, printableResource, cmd.OutOrStdout()) if err != nil { - error_helpers.ShowError(ctx, err) - exitCode = pconstants.ExitCodeUnknownErrorPanic + exitCode = pconstants.ExitCodeOutputRenderingFailed + return } } @@ -123,13 +132,23 @@ func sourceShowCmd() *cobra.Command { func runSourceShowCmd(cmd *cobra.Command, args []string) { //setup a cancel context and start cancel handler ctx, cancel := context.WithCancel(cmd.Context()) + //TODO: https://github.com/turbot/tailpipe/issues/563 none of the functions called in this command will return a cancellation error. Cancellation won't work right now contexthelpers.StartCancelHandler(cancel) utils.LogTime("runSourceShowCmd start") + var err error defer func() { utils.LogTime("runSourceShowCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("tailpipe source show command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForSourceError(err) } }() @@ -152,7 +171,18 @@ func runSourceShowCmd(cmd *cobra.Command, args []string) { // Print err = printer.PrintResource(ctx, printableResource, cmd.OutOrStdout()) if err != nil { - error_helpers.ShowError(ctx, err) - exitCode = pconstants.ExitCodeUnknownErrorPanic + exitCode = pconstants.ExitCodeOutputRenderingFailed + return + } +} + +func setExitCodeForSourceError(err error) { + if exitCode != 0 || err == nil { + return + } + if error_helpers.IsCancelledError(err) { + exitCode = pconstants.ExitCodeOperationCancelled + return } + exitCode = 1 } diff --git a/cmd/table.go b/cmd/table.go index 403b467c..5200e0b9 100644 --- a/cmd/table.go +++ b/cmd/table.go @@ -12,13 +12,13 @@ import ( "github.com/turbot/pipe-fittings/v2/cmdconfig" pconstants "github.com/turbot/pipe-fittings/v2/constants" "github.com/turbot/pipe-fittings/v2/contexthelpers" - "github.com/turbot/pipe-fittings/v2/error_helpers" "github.com/turbot/pipe-fittings/v2/printers" "github.com/turbot/pipe-fittings/v2/utils" localcmdconfig "github.com/turbot/tailpipe/internal/cmdconfig" "github.com/turbot/tailpipe/internal/constants" "github.com/turbot/tailpipe/internal/database" "github.com/turbot/tailpipe/internal/display" + "github.com/turbot/tailpipe/internal/error_helpers" ) func tableCmd() *cobra.Command { @@ -72,11 +72,20 @@ func runTableListCmd(cmd *cobra.Command, args []string) { ctx, cancel := context.WithCancel(cmd.Context()) contexthelpers.StartCancelHandler(cancel) utils.LogTime("runSourceListCmd start") + var err error defer func() { utils.LogTime("runSourceListCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("tailpipe table list command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForTableError(err) } }() @@ -103,8 +112,8 @@ func runTableListCmd(cmd *cobra.Command, args []string) { // Print err = printer.PrintResource(ctx, printableResource, cmd.OutOrStdout()) if err != nil { - error_helpers.ShowError(ctx, err) - exitCode = pconstants.ExitCodeUnknownErrorPanic + exitCode = pconstants.ExitCodeOutputRenderingFailed + return } } @@ -133,11 +142,20 @@ func runTableShowCmd(cmd *cobra.Command, args []string) { ctx, cancel := context.WithCancel(cmd.Context()) contexthelpers.StartCancelHandler(cancel) utils.LogTime("runTableShowCmd start") + var err error defer func() { utils.LogTime("runTableShowCmd end") if r := recover(); r != nil { - error_helpers.ShowError(ctx, helpers.ToError(r)) - exitCode = pconstants.ExitCodeUnknownErrorPanic + err = helpers.ToError(r) + } + if err != nil { + if error_helpers.IsCancelledError(err) { + //nolint:forbidigo // ui output + fmt.Println("tailpipe table show command cancelled.") + } else { + error_helpers.ShowError(ctx, err) + } + setExitCodeForTableError(err) } }() @@ -164,7 +182,18 @@ func runTableShowCmd(cmd *cobra.Command, args []string) { // Print err = printer.PrintResource(ctx, printableResource, cmd.OutOrStdout()) if err != nil { - error_helpers.ShowError(ctx, err) - exitCode = pconstants.ExitCodeUnknownErrorPanic + exitCode = pconstants.ExitCodeOutputRenderingFailed + return + } +} + +func setExitCodeForTableError(err error) { + if exitCode != 0 || err == nil { + return + } + if error_helpers.IsCancelledError(err) { + exitCode = pconstants.ExitCodeOperationCancelled + return } + exitCode = 1 } diff --git a/go.mod b/go.mod index 0236b25a..c7d1e144 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/charmbracelet/bubbletea v1.2.4 github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 github.com/dustin/go-humanize v1.0.1 + github.com/fatih/color v1.18.0 github.com/fsnotify/fsnotify v1.9.0 github.com/gosuri/uiprogress v0.0.1 github.com/hashicorp/go-hclog v1.6.3 @@ -39,6 +40,7 @@ require ( github.com/hashicorp/go-version v1.7.0 github.com/jedib0t/go-pretty/v6 v6.5.9 github.com/marcboeker/go-duckdb/v2 v2.3.5 + github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 github.com/thediveo/enumflag/v2 v2.0.5 github.com/turbot/tailpipe-plugin-core v0.2.10 golang.org/x/text v0.27.0 @@ -114,7 +116,6 @@ require ( github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gertd/go-pluralize v0.2.1 // indirect @@ -199,7 +200,6 @@ require ( github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/satyrius/gonx v1.4.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect - github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect diff --git a/internal/cmdconfig/diagnostics.go b/internal/cmdconfig/diagnostics.go index b5f4fe9d..ea2d26fa 100644 --- a/internal/cmdconfig/diagnostics.go +++ b/internal/cmdconfig/diagnostics.go @@ -9,8 +9,8 @@ import ( "strings" "github.com/spf13/viper" - "github.com/turbot/pipe-fittings/v2/error_helpers" "github.com/turbot/tailpipe/internal/constants" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" ) // DisplayConfig prints all config set via WorkspaceProfile or HCL options diff --git a/internal/error_helpers/error_helpers.go b/internal/error_helpers/error_helpers.go new file mode 100644 index 00000000..d8936ec5 --- /dev/null +++ b/internal/error_helpers/error_helpers.go @@ -0,0 +1,113 @@ +// Copied from pipe-fittings/error_helpers.go. We handle cancellation differently: +// cancellations are a user choice, so we don't throw an error (normalized to "execution cancelled"). +// +//nolint:forbidigo // TODO: review fmt usage +package error_helpers + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/fatih/color" + "github.com/shiena/ansicolor" + "github.com/spf13/viper" + "github.com/turbot/pipe-fittings/v2/constants" + "github.com/turbot/pipe-fittings/v2/statushooks" +) + +func init() { + color.Output = ansicolor.NewAnsiColorWriter(os.Stderr) +} + +func FailOnError(err error) { + if err != nil { + panic(err) + } +} + +func FailOnErrorWithMessage(err error, message string) { + if err != nil { + panic(fmt.Sprintf("%s: %s", message, err.Error())) + } +} + +func ShowError(ctx context.Context, err error) { + if err == nil { + return + } + statushooks.Done(ctx) + opStream := GetWarningOutputStream() + fmt.Fprintf(opStream, "%s: %v\n", constants.ColoredErr, TransformErrorToTailpipe(err)) +} + +// ShowErrorWithMessage displays the given error nicely with the given message +func ShowErrorWithMessage(ctx context.Context, err error, message string) { + if err == nil { + return + } + statushooks.Done(ctx) + opStream := GetWarningOutputStream() + fmt.Fprintf(opStream, "%s: %s - %v\n", constants.ColoredErr, message, TransformErrorToTailpipe(err)) +} + +// TransformErrorToTailpipe removes the pq: and rpc error prefixes along +// with all the unnecessary information that comes from the +// drivers and libraries +func TransformErrorToTailpipe(err error) error { + if err == nil { + return nil + } + + var errString string + if strings.Contains(err.Error(), "flowpipe service is unreachable") { + errString = strings.Split(err.Error(), ": ")[1] + } else { + errString = strings.TrimSpace(err.Error()) + } + + // an error that originated from our database/sql driver (always prefixed with "ERROR:") + if strings.HasPrefix(errString, "ERROR:") { + errString = strings.TrimSpace(strings.TrimPrefix(errString, "ERROR:")) + } + // if this is an RPC Error while talking with the plugin + if strings.HasPrefix(errString, "rpc error") { + // trim out "rpc error: code = Unknown desc =" + errString = strings.TrimPrefix(errString, "rpc error: code = Unknown desc =") + } + return errors.New(strings.TrimSpace(errString)) +} + +func IsCancelledError(err error) bool { + return errors.Is(err, context.Canceled) || strings.Contains(err.Error(), "canceling statement due to user request") +} + +func ShowWarning(warning string) { + if len(warning) == 0 { + return + } + opStream := GetWarningOutputStream() + fmt.Fprintf(opStream, "%s: %v\n", constants.ColoredWarn, warning) +} + +func PrefixError(err error, prefix string) error { + return fmt.Errorf("%s: %s\n", prefix, TransformErrorToTailpipe(err).Error()) +} + +// isMachineReadableOutput checks if the current output format is machine readable (CSV or JSON) +func isMachineReadableOutput() bool { + outputFormat := viper.GetString(constants.ArgOutput) + return outputFormat == constants.OutputFormatCSV || outputFormat == constants.OutputFormatJSON +} + +func GetWarningOutputStream() io.Writer { + if isMachineReadableOutput() { + // For machine-readable formats, output warnings and errors to stderr + return os.Stderr + } + // For all other formats, use stdout + return os.Stdout +} diff --git a/internal/interactive/interactive_client.go b/internal/interactive/interactive_client.go index 753f0b15..6e05cafc 100644 --- a/internal/interactive/interactive_client.go +++ b/internal/interactive/interactive_client.go @@ -17,10 +17,10 @@ import ( "github.com/spf13/viper" "github.com/turbot/go-kit/helpers" pconstants "github.com/turbot/pipe-fittings/v2/constants" - "github.com/turbot/pipe-fittings/v2/error_helpers" "github.com/turbot/pipe-fittings/v2/statushooks" "github.com/turbot/pipe-fittings/v2/utils" "github.com/turbot/tailpipe/internal/database" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" "github.com/turbot/tailpipe/internal/metaquery" "github.com/turbot/tailpipe/internal/query" ) @@ -346,7 +346,7 @@ func (c *InteractiveClient) executor(ctx context.Context, line string) { func (c *InteractiveClient) executeQuery(ctx context.Context, queryCtx context.Context, resolvedQuery *ResolvedQuery) { _, err := query.ExecuteQuery(queryCtx, resolvedQuery.ExecuteSQL, c.db) if err != nil { - error_helpers.ShowError(ctx, error_helpers.HandleCancelError(err)) + error_helpers.ShowError(ctx, err) } } diff --git a/internal/interactive/run.go b/internal/interactive/run.go index 2bfa5faa..4c17435e 100644 --- a/internal/interactive/run.go +++ b/internal/interactive/run.go @@ -3,8 +3,8 @@ package interactive import ( "context" - "github.com/turbot/pipe-fittings/v2/error_helpers" "github.com/turbot/tailpipe/internal/database" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" ) // RunInteractivePrompt starts the interactive query prompt diff --git a/internal/plugin/errors.go b/internal/plugin/errors.go index 00c145fa..ce534b08 100644 --- a/internal/plugin/errors.go +++ b/internal/plugin/errors.go @@ -2,7 +2,6 @@ package plugin import ( "errors" - "github.com/turbot/pipe-fittings/v2/error_helpers" "strings" ) @@ -13,8 +12,6 @@ func cleanupPluginError(err error) error { if err == nil { return nil } - // transform to a context - err = error_helpers.HandleCancelError(err) errString := strings.TrimSpace(err.Error()) diff --git a/internal/plugin/plugin_manager.go b/internal/plugin/plugin_manager.go index 0080f265..2a619bfc 100644 --- a/internal/plugin/plugin_manager.go +++ b/internal/plugin/plugin_manager.go @@ -3,8 +3,6 @@ package plugin import ( "context" "fmt" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" "log/slog" "math/rand/v2" "os" @@ -14,6 +12,9 @@ import ( "sync" "time" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "github.com/hashicorp/go-hclog" goplugin "github.com/hashicorp/go-plugin" "github.com/hashicorp/go-version" @@ -22,7 +23,6 @@ import ( gokithelpers "github.com/turbot/go-kit/helpers" "github.com/turbot/pipe-fittings/v2/app_specific" pconstants "github.com/turbot/pipe-fittings/v2/constants" - "github.com/turbot/pipe-fittings/v2/error_helpers" pfilepaths "github.com/turbot/pipe-fittings/v2/filepaths" "github.com/turbot/pipe-fittings/v2/installationstate" pociinstaller "github.com/turbot/pipe-fittings/v2/ociinstaller" @@ -38,6 +38,7 @@ import ( "github.com/turbot/tailpipe-plugin-sdk/types" "github.com/turbot/tailpipe/internal/config" "github.com/turbot/tailpipe/internal/constants" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" "github.com/turbot/tailpipe/internal/helpers" "github.com/turbot/tailpipe/internal/ociinstaller" "google.golang.org/protobuf/types/known/timestamppb" @@ -151,7 +152,7 @@ func (p *PluginManager) Collect(ctx context.Context, partition *config.Partition collectResponse, err := tablePluginClient.Collect(req) if err != nil { - return nil, fmt.Errorf("error starting collection for plugin %s: %w", tablePluginClient.Name, error_helpers.TransformErrorToSteampipe(err)) + return nil, fmt.Errorf("error starting collection for plugin %s: %w", tablePluginClient.Name, error_helpers.TransformErrorToTailpipe(err)) } // start a goroutine to read the eventStream and listen to file events @@ -219,7 +220,7 @@ func (p *PluginManager) getSupportedOperations(tablePluginClient *grpc.PluginCli } // Describe starts the plugin if needed, and returns the plugin description, including description of any custom formats -func (p *PluginManager) Describe(ctx context.Context, pluginName string, opts ...DescribeOpts) (*types.DescribeResponse, error) { +func (p *PluginManager) Describe(_ context.Context, pluginName string, opts ...DescribeOpts) (*types.DescribeResponse, error) { // build plugin ref from the name pluginDef := pplugin.NewPlugin(pluginName) @@ -278,7 +279,7 @@ func (p *PluginManager) UpdateCollectionState(ctx context.Context, partition *co _, err = pluginClient.UpdateCollectionState(req) if err != nil { - return fmt.Errorf("error updating collection state for plugin %s: %w", pluginClient.Name, error_helpers.TransformErrorToSteampipe(err)) + return fmt.Errorf("error updating collection state for plugin %s: %w", pluginClient.Name, error_helpers.TransformErrorToTailpipe(err)) } // just return - the observer is responsible for waiting for completion diff --git a/internal/query/execute.go b/internal/query/execute.go index 31c73877..653dd7cf 100644 --- a/internal/query/execute.go +++ b/internal/query/execute.go @@ -9,7 +9,6 @@ import ( "strings" "time" - "github.com/turbot/pipe-fittings/v2/error_helpers" "github.com/turbot/pipe-fittings/v2/query" "github.com/turbot/pipe-fittings/v2/querydisplay" "github.com/turbot/pipe-fittings/v2/queryresult" @@ -17,6 +16,7 @@ import ( "github.com/turbot/pipe-fittings/v2/utils" "github.com/turbot/tailpipe/internal/config" "github.com/turbot/tailpipe/internal/database" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" ) func RunBatchSession(ctx context.Context, args []string, db *database.DuckDb) (int, []error) { diff --git a/main.go b/main.go index f923bd1e..dbd800f9 100644 --- a/main.go +++ b/main.go @@ -7,11 +7,11 @@ import ( "github.com/spf13/viper" "github.com/turbot/go-kit/helpers" "github.com/turbot/pipe-fittings/v2/constants" - "github.com/turbot/pipe-fittings/v2/error_helpers" "github.com/turbot/pipe-fittings/v2/utils" "github.com/turbot/tailpipe/cmd" "github.com/turbot/tailpipe/internal/cmdconfig" localconstants "github.com/turbot/tailpipe/internal/constants" + error_helpers "github.com/turbot/tailpipe/internal/error_helpers" ) var exitCode int