mirror of
https://github.com/turbot/steampipe.git
synced 2025-12-19 09:58:53 -05:00
* Add test demonstrating race condition in AddCommands/ResetCommands Added TestAddCommands_Concurrent which exposes data races when AddCommands() and ResetCommands() are called concurrently. Running with -race flag shows multiple race condition warnings. This test demonstrates bug #4708 where these functions are not thread-safe. While not a practical issue (only called during single-threaded CLI initialization), proper synchronization should be added. Related to #4708 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix race condition in AddCommands/ResetCommands with mutex Added thread-safe synchronization to AddCommands() and created a new thread-safe ResetCommands() wrapper using a shared mutex. The underlying cobra.Command methods are not thread-safe, causing data races when called concurrently. While these functions are typically only called during single-threaded CLI initialization, adding proper synchronization ensures correctness and allows for safe concurrent usage in tests. Changes: - Added commandMutex to protect concurrent access to rootCmd - Updated AddCommands() with mutex lock/unlock - Created ResetCommands() wrapper with mutex protection - Updated test to use the new thread-safe ResetCommands() Test now passes with -race flag with no warnings. Fixes #4708 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
148 lines
4.4 KiB
Go
148 lines
4.4 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"sync"
|
|
|
|
"github.com/mattn/go-isatty"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
filehelpers "github.com/turbot/go-kit/files"
|
|
"github.com/turbot/pipe-fittings/v2/app_specific"
|
|
"github.com/turbot/pipe-fittings/v2/constants"
|
|
"github.com/turbot/pipe-fittings/v2/utils"
|
|
"github.com/turbot/steampipe/v2/pkg/error_helpers"
|
|
"github.com/turbot/steampipe/v2/pkg/statushooks"
|
|
)
|
|
|
|
var exitCode int
|
|
|
|
// commandMutex protects concurrent access to rootCmd's command list
|
|
var commandMutex sync.Mutex
|
|
|
|
// rootCmd represents the base command when called without any subcommands
|
|
var rootCmd = &cobra.Command{
|
|
Use: "steampipe [--version] [--help] COMMAND [args]",
|
|
Short: "Query cloud resources using SQL",
|
|
Long: `Steampipe: select * from cloud;
|
|
|
|
Dynamically query APIs, code and more with SQL.
|
|
Zero-ETL from 140+ data sources.
|
|
|
|
Common commands:
|
|
|
|
# Interactive SQL query console
|
|
steampipe query
|
|
|
|
# Install a plugin from the hub - https://hub.steampipe.io
|
|
steampipe plugin install aws
|
|
|
|
# Execute a defined SQL query
|
|
steampipe query "select * from aws_s3_bucket"
|
|
|
|
# Get help for a command
|
|
steampipe help query
|
|
|
|
Documentation: https://steampipe.io/docs
|
|
`,
|
|
}
|
|
|
|
func InitCmd() {
|
|
utils.LogTime("cmd.root.InitCmd start")
|
|
defer utils.LogTime("cmd.root.InitCmd end")
|
|
|
|
defaultInstallDir, err := filehelpers.Tildefy(app_specific.DefaultInstallDir)
|
|
error_helpers.FailOnError(err)
|
|
|
|
// Set the version after viper has been initialized
|
|
rootCmd.Version = viper.GetString("main.version")
|
|
rootCmd.SetVersionTemplate("Steampipe v{{.Version}}\n")
|
|
|
|
// global flags
|
|
rootCmd.PersistentFlags().String(constants.ArgWorkspaceProfile, "default", "The workspace profile to use") // workspace profile profile is a global flag since install-dir(global) can be set through the workspace profile
|
|
rootCmd.PersistentFlags().String(constants.ArgInstallDir, defaultInstallDir, "Path to the Config Directory")
|
|
rootCmd.PersistentFlags().Bool(constants.ArgSchemaComments, true, "Include schema comments when importing connection schemas")
|
|
|
|
error_helpers.FailOnError(viper.BindPFlag(constants.ArgInstallDir, rootCmd.PersistentFlags().Lookup(constants.ArgInstallDir)))
|
|
error_helpers.FailOnError(viper.BindPFlag(constants.ArgWorkspaceProfile, rootCmd.PersistentFlags().Lookup(constants.ArgWorkspaceProfile)))
|
|
error_helpers.FailOnError(viper.BindPFlag(constants.ArgSchemaComments, rootCmd.PersistentFlags().Lookup(constants.ArgSchemaComments)))
|
|
|
|
AddCommands()
|
|
|
|
// disable auto completion generation, since we don't want to support
|
|
// powershell yet - and there's no way to disable powershell in the default generator
|
|
rootCmd.CompletionOptions.DisableDefaultCmd = true
|
|
rootCmd.Flags().BoolP(constants.ArgHelp, "h", false, "Help for steampipe")
|
|
|
|
hideRootFlags(constants.ArgSchemaComments)
|
|
|
|
// tell OS to reclaim memory immediately
|
|
os.Setenv("GODEBUG", "madvdontneed=1")
|
|
|
|
}
|
|
|
|
func hideRootFlags(flags ...string) {
|
|
for _, flag := range flags {
|
|
if f := rootCmd.Flag(flag); f != nil {
|
|
f.Hidden = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddCommands adds all subcommands to the root command.
|
|
//
|
|
// This function is thread-safe and can be called concurrently.
|
|
// However, it is typically only called during CLI initialization
|
|
// in a single-threaded context.
|
|
func AddCommands() {
|
|
commandMutex.Lock()
|
|
defer commandMutex.Unlock()
|
|
|
|
// explicitly initialise commands here rather than in init functions to allow us to handle errors from the config load
|
|
rootCmd.AddCommand(
|
|
pluginCmd(),
|
|
queryCmd(),
|
|
serviceCmd(),
|
|
generateCompletionScriptsCmd(),
|
|
pluginManagerCmd(),
|
|
loginCmd(),
|
|
)
|
|
}
|
|
|
|
// ResetCommands removes all subcommands from the root command.
|
|
//
|
|
// This function is thread-safe and can be called concurrently.
|
|
// It is primarily used for testing.
|
|
func ResetCommands() {
|
|
commandMutex.Lock()
|
|
defer commandMutex.Unlock()
|
|
|
|
rootCmd.ResetCommands()
|
|
}
|
|
|
|
func Execute() int {
|
|
utils.LogTime("cmd.root.Execute start")
|
|
defer utils.LogTime("cmd.root.Execute end")
|
|
|
|
ctx := createRootContext()
|
|
|
|
err := rootCmd.ExecuteContext(ctx)
|
|
if err != nil {
|
|
exitCode = 1
|
|
}
|
|
return exitCode
|
|
}
|
|
|
|
// create the root context - add a status renderer
|
|
func createRootContext() context.Context {
|
|
statusRenderer := statushooks.NullHooks
|
|
// if the client is a TTY, inject a status spinner
|
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
|
statusRenderer = statushooks.NewStatusSpinnerHook()
|
|
}
|
|
|
|
ctx := statushooks.AddStatusHooksToContext(context.Background(), statusRenderer)
|
|
return ctx
|
|
}
|