diff --git a/cmd/check.go b/cmd/check.go index 9bc0a8ab6..6ebae66e4 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -87,8 +87,10 @@ You may specify one or more benchmarks or controls to run (separated by a space) return cmd } -// exitCode=1 For unknown errors resulting in panics -// exitCode=2 For insufficient args +// exitCode=0 no runtime errors, no control alarms or errors +// exitCode=1 no runtime errors, 1 or more control alarms, no control errors +// exitCode=2 no runtime errors, 1 or more control errors +// exitCode=3+ runtime errors func runCheckCmd(cmd *cobra.Command, args []string) { utils.LogTime("runCheckCmd start") @@ -109,7 +111,7 @@ func runCheckCmd(cmd *cobra.Command, args []string) { // verify we have an argument if !validateCheckArgs(ctx, cmd, args) { - exitCode = constants.ExitCodeInsufficientOrWrongArguments + exitCode = constants.ExitCodeInsufficientOrWrongInputs return } // if diagnostic mode is set, print out config and return @@ -120,7 +122,11 @@ func runCheckCmd(cmd *cobra.Command, args []string) { // initialise initData := control.NewInitData(ctx) - error_helpers.FailOnError(initData.Result.Error) + if initData.Result.Error != nil { + exitCode = constants.ExitCodeInitializationFailed + error_helpers.ShowError(ctx, initData.Result.Error) + return + } defer initData.Cleanup(ctx) // if there is a usage warning we display it @@ -129,7 +135,7 @@ func runCheckCmd(cmd *cobra.Command, args []string) { // pull out useful properties w := initData.Workspace client := initData.Client - failures := 0 + totalAlarms, totalErrors := 0, 0 var durations []time.Duration shouldShare := viper.GetBool(constants.ArgShare) @@ -149,8 +155,11 @@ func runCheckCmd(cmd *cobra.Command, args []string) { executionTree, err := controlexecute.NewExecutionTree(ctx, w, client, targetName, initData.ControlFilterWhereClause) error_helpers.FailOnError(err) - // execute controls synchronously (execute returns the number of failures) - failures += executionTree.Execute(ctx) + // execute controls synchronously (execute returns the number of alarms and errors) + stats := executionTree.Execute(ctx) + // append the total number of alarms and errors for multiple runs + totalAlarms += stats.Alarm + totalErrors += stats.Error err = displayControlResults(ctx, executionTree, initData.OutputFormatter) error_helpers.FailOnError(err) @@ -165,7 +174,10 @@ func runCheckCmd(cmd *cobra.Command, args []string) { // if the share args are set, create a snapshot and share it if generateSnapshot { err = controldisplay.PublishSnapshot(ctx, executionTree, shouldShare) - error_helpers.FailOnError(err) + if err != nil { + exitCode = constants.ExitCodeSnapshotUploadFailed + error_helpers.FailOnError(err) + } } durations = append(durations, executionTree.EndTime.Sub(executionTree.StartTime)) @@ -174,6 +186,23 @@ func runCheckCmd(cmd *cobra.Command, args []string) { if shouldPrintTiming() { printTiming(args, durations) } + + // set the defined exit code after successful execution + exitCode = getExitCode(totalAlarms, totalErrors) +} + +// get the exit code for successful check run +func getExitCode(alarms int, errors int) int { + // 1 or more control errors, return exitCode=2 + if errors > 0 { + return constants.ExitCodeControlsError + } + // 1 or more controls in alarm, return exitCode=1 + if alarms > 0 { + return constants.ExitCodeControlsAlarm + } + // no controls in alarm/error + return constants.ExitCodeSuccessful } // create the context for the check run - add a control status renderer diff --git a/cmd/dashboard.go b/cmd/dashboard.go index a19aa72c6..025493e5f 100644 --- a/cmd/dashboard.go +++ b/cmd/dashboard.go @@ -140,7 +140,10 @@ func runDashboardCmd(cmd *cobra.Command, args []string) { // load the workspace initData := initDashboard(dashboardCtx) defer initData.Cleanup(dashboardCtx) - error_helpers.FailOnError(initData.Result.Error) + if initData.Result.Error != nil { + exitCode = constants.ExitCodeInitializationFailed + error_helpers.FailOnError(initData.Result.Error) + } // if there is a usage warning we display it initData.Result.DisplayMessage = dashboardserver.OutputMessage @@ -286,6 +289,7 @@ func runSingleDashboard(ctx context.Context, targetName string, inputs map[strin // so a dashboard name was specified - just call GenerateSnapshot snap, err := dashboardexecute.GenerateSnapshot(ctx, targetName, initData, inputs) if err != nil { + exitCode = constants.ExitCodeSnapshotCreationFailed return err } // display the snapshot result (if needed) @@ -293,7 +297,10 @@ func runSingleDashboard(ctx context.Context, targetName string, inputs map[strin // upload the snapshot (if needed) err = publishSnapshotIfNeeded(ctx, snap) - error_helpers.FailOnErrorWithMessage(err, fmt.Sprintf("failed to publish snapshot to %s", viper.GetString(constants.ArgSnapshotLocation))) + if err != nil { + exitCode = constants.ExitCodeSnapshotUploadFailed + error_helpers.FailOnErrorWithMessage(err, fmt.Sprintf("failed to publish snapshot to %s", viper.GetString(constants.ArgSnapshotLocation))) + } // export the result (if needed) exportArgs := viper.GetStringSlice(constants.ArgExport) diff --git a/cmd/login.go b/cmd/login.go index 336f15577..cbf96d558 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -37,14 +37,26 @@ func runLoginCmd(cmd *cobra.Command, _ []string) { log.Printf("[TRACE] opening login web page") // start login flow - this will open a web page prompting user to login, and will give the user a code to enter var id, err = cloud.WebLogin(ctx) - error_helpers.FailOnError(err) + if err != nil { + error_helpers.ShowError(ctx, err) + exitCode = constants.ExitCodeLoginCloudConnectionFailed + return + } token, err := getToken(ctx, id) - error_helpers.FailOnError(err) + if err != nil { + error_helpers.ShowError(ctx, err) + exitCode = constants.ExitCodeLoginCloudConnectionFailed + return + } // save token err = cloud.SaveToken(token) - error_helpers.FailOnError(err) + if err != nil { + error_helpers.ShowError(ctx, err) + exitCode = constants.ExitCodeLoginCloudConnectionFailed + return + } displayLoginMessage(ctx, token) } @@ -80,8 +92,6 @@ func getToken(ctx context.Context, id string) (loginToken string, err error) { } log.Printf("[TRACE] Retrying") } - - return } func displayLoginMessage(ctx context.Context, token string) { diff --git a/cmd/plugin.go b/cmd/plugin.go index 3caecc918..d400311fa 100644 --- a/cmd/plugin.go +++ b/cmd/plugin.go @@ -209,7 +209,7 @@ func runPluginInstallCmd(cmd *cobra.Command, args []string) { fmt.Println() cmd.Help() fmt.Println() - exitCode = constants.ExitCodeInsufficientOrWrongArguments + exitCode = constants.ExitCodeInsufficientOrWrongInputs return } @@ -303,13 +303,14 @@ func runPluginUpdateCmd(cmd *cobra.Command, args []string) { fmt.Println() cmd.Help() fmt.Println() - exitCode = constants.ExitCodeInsufficientOrWrongArguments + exitCode = constants.ExitCodeInsufficientOrWrongInputs return } if len(plugins) > 0 && !(cmdconfig.Viper().GetBool("all")) && plugins[0] == "all" { // improve the response to wrong argument "steampipe plugin update all" fmt.Println() + exitCode = constants.ExitCodeInsufficientOrWrongInputs error_helpers.ShowError(ctx, fmt.Errorf("Did you mean %s?", constants.Bold("--all"))) fmt.Println() return @@ -318,7 +319,7 @@ func runPluginUpdateCmd(cmd *cobra.Command, args []string) { state, err := statefile.LoadState() if err != nil { error_helpers.ShowError(ctx, fmt.Errorf("could not load state")) - exitCode = constants.ExitCodeLoadingError + exitCode = constants.ExitCodePluginLoadingError return } @@ -326,7 +327,7 @@ func runPluginUpdateCmd(cmd *cobra.Command, args []string) { versionData, err := versionfile.LoadPluginVersionFile() if err != nil { error_helpers.ShowError(ctx, fmt.Errorf("error loading current plugin data")) - exitCode = constants.ExitCodeLoadingError + exitCode = constants.ExitCodePluginLoadingError return } @@ -353,6 +354,7 @@ func runPluginUpdateCmd(cmd *cobra.Command, args []string) { if isExists { runUpdatesFor = append(runUpdatesFor, versionData.Plugins[ref.DisplayImageRef()]) } else { + exitCode = constants.ExitCodePluginNotFound updateResults = append(updateResults, &display.PluginInstallReport{ Skipped: true, Plugin: p, @@ -383,7 +385,7 @@ func runPluginUpdateCmd(cmd *cobra.Command, args []string) { // this happens if for some reason the update server could not be contacted, // in which case we get back an empty map error_helpers.ShowError(ctx, fmt.Errorf("there was an issue contacting the update server, please try later")) - exitCode = constants.ExitCodeLoadingError + exitCode = constants.ExitCodePluginLoadingError return } @@ -477,6 +479,7 @@ func installPlugin(ctx context.Context, pluginName string, isUpdate bool, bar *u msg := "" _, name, stream := ociinstaller.NewSteampipeImageRef(pluginName).GetOrgNameAndStream() if isPluginNotFoundErr(err) { + exitCode = constants.ExitCodePluginNotFound msg = constants.PluginNotFound } else { msg = err.Error() @@ -652,7 +655,7 @@ func runPluginUninstallCmd(cmd *cobra.Command, args []string) { fmt.Println() cmd.Help() fmt.Println() - exitCode = constants.ExitCodeInsufficientOrWrongArguments + exitCode = constants.ExitCodeInsufficientOrWrongInputs return } @@ -668,6 +671,9 @@ func runPluginUninstallCmd(cmd *cobra.Command, args []string) { for _, p := range args { spinner.SetStatus(fmt.Sprintf("Uninstalling %s", p)) if report, err := plugin.Remove(ctx, p, connectionMap); err != nil { + if strings.Contains(err.Error(), "not found") { + exitCode = constants.ExitCodePluginNotFound + } error_helpers.ShowErrorWithMessage(ctx, err, fmt.Sprintf("Failed to uninstall plugin '%s'", p)) } else { report.ShortName = p diff --git a/cmd/query.go b/cmd/query.go index 9522c3006..689f05a18 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -128,7 +128,11 @@ func runQueryCmd(cmd *cobra.Command, args []string) { // start the initializer initData := query.NewInitData(ctx, args) - error_helpers.FailOnError(initData.Result.Error) + if initData.Result.Error != nil { + exitCode = constants.ExitCodeInitializationFailed + error_helpers.ShowError(ctx, initData.Result.Error) + return + } defer initData.Cleanup(ctx) switch { @@ -137,34 +141,44 @@ func runQueryCmd(cmd *cobra.Command, args []string) { case snapshotRequired(): // if we are either outputting snapshot format, or sharing the results as a snapshot, execute the query // as a dashboard - exitCode = executeSnapshotQuery(initData, ctx) + failures := executeSnapshotQuery(initData, ctx) + if failures != 0 { + exitCode = constants.ExitCodeQueryExecutionFailed + } default: // NOTE: disable any status updates - we do not want 'loading' output from any queries ctx = statushooks.DisableStatusHooks(ctx) // fall through to running a batch query // set global exit code - exitCode = queryexecute.RunBatchSession(ctx, initData) + failures := queryexecute.RunBatchSession(ctx, initData) + if failures != 0 { + exitCode = constants.ExitCodeQueryExecutionFailed + } } } func validateQueryArgs(ctx context.Context, args []string) error { interactiveMode := len(args) == 0 if interactiveMode && (viper.IsSet(constants.ArgSnapshot) || viper.IsSet(constants.ArgShare)) { + exitCode = constants.ExitCodeInsufficientOrWrongInputs return fmt.Errorf("cannot share snapshots in interactive mode") } if interactiveMode && len(viper.GetStringSlice(constants.ArgExport)) > 0 { + exitCode = constants.ExitCodeInsufficientOrWrongInputs return fmt.Errorf("cannot export query results in interactive mode") } // if share or snapshot args are set, there must be a query specified err := cmdconfig.ValidateSnapshotArgs(ctx) if err != nil { + exitCode = constants.ExitCodeInsufficientOrWrongInputs return err } validOutputFormats := []string{constants.OutputFormatLine, constants.OutputFormatCSV, constants.OutputFormatTable, constants.OutputFormatJSON, constants.OutputFormatSnapshot, constants.OutputFormatSnapshotShort, constants.OutputFormatNone} output := viper.GetString(constants.ArgOutput) if !helpers.StringSliceContains(validOutputFormats, output) { + exitCode = constants.ExitCodeInsufficientOrWrongInputs return fmt.Errorf("invalid output format: '%s', must be one of [%s]", output, strings.Join(validOutputFormats, ", ")) } @@ -198,7 +212,10 @@ func executeSnapshotQuery(initData *query.InitData, ctx context.Context) int { // so a dashboard name was specified - just call GenerateSnapshot snap, err := dashboardexecute.GenerateSnapshot(ctx, queryProvider.Name(), baseInitData, nil) - error_helpers.FailOnError(err) + if err != nil { + exitCode = constants.ExitCodeSnapshotCreationFailed + error_helpers.FailOnError(err) + } // set the filename root for the snapshot (in case needed) if !existingResource { @@ -225,7 +242,10 @@ func executeSnapshotQuery(initData *query.InitData, ctx context.Context) int { // share the snapshot if necessary err = publishSnapshotIfNeeded(ctx, snap) - error_helpers.FailOnErrorWithMessage(err, fmt.Sprintf("failed to publish snapshot to %s", viper.GetString(constants.ArgSnapshotLocation))) + if err != nil { + exitCode = constants.ExitCodeSnapshotUploadFailed + error_helpers.FailOnErrorWithMessage(err, fmt.Sprintf("failed to publish snapshot to %s", viper.GetString(constants.ArgSnapshotLocation))) + } // export the result if necessary exportArgs := viper.GetStringSlice(constants.ArgExport) diff --git a/cmd/service.go b/cmd/service.go index d55ad424f..c2ae5dd19 100644 --- a/cmd/service.go +++ b/cmd/service.go @@ -164,24 +164,38 @@ func runServiceStartCmd(cmd *cobra.Command, args []string) { port := viper.GetInt(constants.ArgDatabasePort) if port < 1 || port > 65535 { + exitCode = constants.ExitCodeInsufficientOrWrongInputs panic("Invalid port - must be within range (1:65535)") } serviceListen := db_local.StartListenType(viper.GetString(constants.ArgListenAddress)) - error_helpers.FailOnError(serviceListen.IsValid()) + if serviceListen.IsValid() != nil { + exitCode = constants.ExitCodeInsufficientOrWrongInputs + error_helpers.FailOnError(serviceListen.IsValid()) + } invoker := constants.Invoker(cmdconfig.Viper().GetString(constants.ArgInvoker)) - error_helpers.FailOnError(invoker.IsValid()) + if invoker.IsValid() != nil { + exitCode = constants.ExitCodeInsufficientOrWrongInputs + error_helpers.FailOnError(invoker.IsValid()) + } err := db_local.EnsureDBInstalled(ctx) - error_helpers.FailOnError(err) + if err != nil { + exitCode = constants.ExitCodeServiceStartupFailure + error_helpers.FailOnError(err) + } // start db, refreshing connections startResult := db_local.StartServices(ctx, port, serviceListen, invoker) - error_helpers.FailOnError(startResult.Error) + if startResult.Error != nil { + exitCode = constants.ExitCodeServiceSetupFailure + error_helpers.FailOnError(startResult.Error) + } if startResult.Status == db_local.ServiceFailedToStart { error_helpers.ShowError(ctx, fmt.Errorf("steampipe service failed to start")) + exitCode = constants.ExitCodeServiceStartupFailure return } @@ -189,9 +203,11 @@ func runServiceStartCmd(cmd *cobra.Command, args []string) { // check that we have the same port and listen parameters if port != startResult.DbState.Port { + exitCode = constants.ExitCodeInsufficientOrWrongInputs error_helpers.FailOnError(fmt.Errorf("service is already running on port %d - cannot change port while it's running", startResult.DbState.Port)) } if serviceListen != startResult.DbState.ListenType { + exitCode = constants.ExitCodeInsufficientOrWrongInputs error_helpers.FailOnError(fmt.Errorf("service is already running and listening on %s - cannot change listen type while it's running", startResult.DbState.ListenType)) } @@ -199,6 +215,7 @@ func runServiceStartCmd(cmd *cobra.Command, args []string) { startResult.DbState.Invoker = constants.InvokerService err = startResult.DbState.Save() if err != nil { + exitCode = constants.ExitCodeFileSystemAccessFailure error_helpers.FailOnErrorWithMessage(err, "service was already running, but could not make it persistent") } } @@ -211,6 +228,7 @@ func runServiceStartCmd(cmd *cobra.Command, args []string) { _, err1 := db_local.StopServices(ctx, false, constants.InvokerService) if err1 != nil { error_helpers.ShowError(ctx, err1) + exitCode = constants.ExitCodeServiceSetupFailure } error_helpers.FailOnError(err) } @@ -373,7 +391,11 @@ func runServiceRestartCmd(cmd *cobra.Command, args []string) { // stop db stopStatus, err := db_local.StopServices(ctx, viper.GetBool(constants.ArgForce), constants.InvokerService) - error_helpers.FailOnErrorWithMessage(err, "could not stop current instance") + if err != nil { + exitCode = constants.ExitCodeServiceStopFailure + error_helpers.FailOnErrorWithMessage(err, "could not stop current instance") + } + if stopStatus != db_local.ServiceStopped { fmt.Println(` Service stop failed. @@ -388,7 +410,10 @@ to force a restart. // stop the running dashboard server err = dashboardserver.StopDashboardService(ctx) - error_helpers.FailOnErrorWithMessage(err, "could not stop dashboard service") + if err != nil { + exitCode = constants.ExitCodeServiceStopFailure + error_helpers.FailOnErrorWithMessage(err, "could not stop dashboard service") + } // set the password in 'viper' so that it can be used by 'service start' viper.Set(constants.ArgServicePassword, currentDbState.Password) @@ -397,13 +422,17 @@ to force a restart. dbStartResult := db_local.StartServices(cmd.Context(), currentDbState.Port, currentDbState.ListenType, currentDbState.Invoker) error_helpers.FailOnError(dbStartResult.Error) if dbStartResult.Status == db_local.ServiceFailedToStart { + exitCode = constants.ExitCodeServiceStartupFailure fmt.Println("Steampipe service was stopped, but failed to restart.") return } // refresh connections err = db_local.RefreshConnectionAndSearchPaths(cmd.Context(), constants.InvokerService) - error_helpers.FailOnError(err) + if err != nil { + exitCode = constants.ExitCodeServiceSetupFailure + error_helpers.FailOnError(err) + } // if the dashboard was running, start it if currentDashboardState != nil { @@ -488,13 +517,22 @@ func runServiceStopCmd(cmd *cobra.Command, args []string) { dashboardStopError := dashboardserver.StopDashboardService(ctx) status, dbStopError = db_local.StopServices(ctx, force, constants.InvokerService) dbStopError = error_helpers.CombineErrors(dbStopError, dashboardStopError) - error_helpers.FailOnError(dbStopError) + if dbStopError != nil { + exitCode = constants.ExitCodeServiceStopFailure + error_helpers.FailOnError(dbStopError) + } } else { dbState, dbStopError = db_local.GetState() - error_helpers.FailOnErrorWithMessage(dbStopError, "could not stop Steampipe service") + if dbStopError != nil { + exitCode = constants.ExitCodeServiceStopFailure + error_helpers.FailOnErrorWithMessage(dbStopError, "could not stop Steampipe service") + } dashboardState, err := dashboardserver.GetDashboardServiceState() - error_helpers.FailOnErrorWithMessage(err, "could not stop Steampipe service") + if err != nil { + exitCode = constants.ExitCodeServiceStopFailure + error_helpers.FailOnErrorWithMessage(err, "could not stop Steampipe service") + } if dbState == nil { fmt.Println("Steampipe service is not running.") @@ -507,12 +545,18 @@ func runServiceStopCmd(cmd *cobra.Command, args []string) { if dashboardState != nil { err = dashboardserver.StopDashboardService(ctx) - error_helpers.FailOnErrorWithMessage(err, "could not stop dashboard server") + if err != nil { + exitCode = constants.ExitCodeServiceStopFailure + error_helpers.FailOnErrorWithMessage(err, "could not stop dashboard server") + } } // check if there are any connected clients to the service connectedClients, err := db_local.GetClientCount(cmd.Context()) - error_helpers.FailOnErrorWithMessage(err, "service stop failed") + if err != nil { + exitCode = constants.ExitCodeServiceStopFailure + error_helpers.FailOnErrorWithMessage(err, "service stop failed") + } if connectedClients.TotalClients > 0 { printClientsConnected() @@ -520,7 +564,10 @@ func runServiceStopCmd(cmd *cobra.Command, args []string) { } status, err = db_local.StopServices(ctx, false, constants.InvokerService) - error_helpers.FailOnErrorWithMessage(err, "service stop failed") + if err != nil { + exitCode = constants.ExitCodeServiceStopFailure + error_helpers.FailOnErrorWithMessage(err, "service stop failed") + } } switch status { diff --git a/pkg/constants/exit_codes.go b/pkg/constants/exit_codes.go index eaa658eda..a172ab446 100644 --- a/pkg/constants/exit_codes.go +++ b/pkg/constants/exit_codes.go @@ -1,11 +1,23 @@ package constants const ( - ExitCodeSuccessful = 0 - ExitCodeUnknownErrorPanic = 1 - ExitCodeInsufficientOrWrongArguments = 2 - ExitCodeLoadingError = 3 - ExitCodePluginListFailure = 4 - ExitCodeNoModFile = 15 - ExitCodeBindPortUnavailable = 31 + ExitCodeSuccessful = 0 + ExitCodeControlsAlarm = 1 // check - no runtime errors, 1 or more control alarms, no control errors + ExitCodeControlsError = 2 // check - no runtime errors, 1 or more control errors + ExitCodePluginLoadingError = 11 // plugin - loading error + ExitCodePluginListFailure = 12 // plugin - listing failed + ExitCodePluginNotFound = 13 // plugin - not found + ExitCodeSnapshotCreationFailed = 21 // snapshot - creation failed + ExitCodeSnapshotUploadFailed = 22 // snapshot - upload failed + ExitCodeServiceSetupFailure = 31 // service - setup failed + ExitCodeServiceStartupFailure = 32 // service - start failed + ExitCodeServiceStopFailure = 33 // service - stop failed + ExitCodeQueryExecutionFailed = 41 // query - 1 or more queries failed - change in behavior(previously the exitCode used to be the number of queries that failed) + ExitCodeLoginCloudConnectionFailed = 51 // login - connecting to cloud failed + ExitCodeInitializationFailed = 250 // common - initialization failed + ExitCodeBindPortUnavailable = 251 // common(service/dashboard) - port binding failed + ExitCodeNoModFile = 252 // common - no mod file + ExitCodeFileSystemAccessFailure = 253 // common - file system access failed + ExitCodeInsufficientOrWrongInputs = 254 // common - runtime error(insufficient or wrong input) + ExitCodeUnknownErrorPanic = 255 // common - runtime error(unknown panic) ) diff --git a/pkg/control/controlexecute/execution_tree.go b/pkg/control/controlexecute/execution_tree.go index ba5455eb9..b5b70dddb 100644 --- a/pkg/control/controlexecute/execution_tree.go +++ b/pkg/control/controlexecute/execution_tree.go @@ -3,11 +3,12 @@ package controlexecute import ( "context" "fmt" - "github.com/turbot/go-kit/helpers" "log" "sort" "time" + "github.com/turbot/go-kit/helpers" + "github.com/spf13/viper" "github.com/turbot/steampipe/pkg/constants" "github.com/turbot/steampipe/pkg/control/controlstatus" @@ -86,7 +87,7 @@ func (e *ExecutionTree) AddControl(ctx context.Context, control *modconfig.Contr } } -func (e *ExecutionTree) Execute(ctx context.Context) int { +func (e *ExecutionTree) Execute(ctx context.Context) controlstatus.StatusSummary { log.Println("[TRACE]", "begin ExecutionTree.Execute") defer log.Println("[TRACE]", "end ExecutionTree.Execute") e.StartTime = time.Now() @@ -113,13 +114,11 @@ func (e *ExecutionTree) Execute(ctx context.Context) int { log.Printf("[WARN] timed out waiting for active runs to complete") } - failures := e.Root.Summary.Status.Alarm + e.Root.Summary.Status.Error - // now build map of dimension property name to property value to color map e.DimensionColorGenerator, _ = NewDimensionColorGenerator(4, 27) e.DimensionColorGenerator.populate(e) - return failures + return e.Root.Summary.Status } func (e *ExecutionTree) waitForActiveRunsToComplete(ctx context.Context, parallelismLock *semaphore.Weighted, maxParallelGoRoutines int64) error { diff --git a/tests/acceptance/test_data/functionality_test_mod/functionality/all_controls_ok.sp b/tests/acceptance/test_data/functionality_test_mod/functionality/all_controls_ok.sp new file mode 100644 index 000000000..7c87e5647 --- /dev/null +++ b/tests/acceptance/test_data/functionality_test_mod/functionality/all_controls_ok.sp @@ -0,0 +1,28 @@ +benchmark "all_controls_ok" { + title = "All controls in OK, no ALARMS/ERORS" + description = "Benchmark to verify the exit code when no controls are in error/alarm" + children = [ + control.ok_1, + control.ok_2 + ] +} + +control "ok_1" { + title = "Control to verify the exit code when no controls are in error/alarm" + description = "Control to verify the exit code when no controls are in error/alarm" + query = query.query_1 + severity = "high" +} + +control "ok_2" { + title = "Control to verify the exit code when no controls are in error/alarm" + description = "Control to verify the exit code when no controls are in error/alarm" + query = query.query_1 + severity = "high" +} + +query "query_1"{ + title ="query_1" + description = "Simple query 1" + sql = "select 'ok' as status, 'steampipe' as resource, 'acceptance tests' as reason" +} \ No newline at end of file diff --git a/tests/acceptance/test_files/check.bats b/tests/acceptance/test_files/check.bats index 120f45f93..70ee871e1 100644 --- a/tests/acceptance/test_files/check.bats +++ b/tests/acceptance/test_files/check.bats @@ -1,10 +1,38 @@ load "$LIB_BATS_ASSERT/load.bash" load "$LIB_BATS_SUPPORT/load.bash" -@test "steampipe check check_rendering_benchmark" { +@test "steampipe check exitCode - no control alarms or errors" { + cd $FUNCTIONALITY_TEST_MOD + run steampipe check benchmark.all_controls_ok + assert_equal $status 0 + cd - +} + +@test "steampipe check exitCode - with controls in error" { cd $CONTROL_RENDERING_TEST_MOD run steampipe check benchmark.control_check_rendering_benchmark - assert_equal $status 0 + assert_equal $status 2 + cd - +} + +@test "steampipe check exitCode - with controls in alarm" { + cd $FUNCTIONALITY_TEST_MOD + run steampipe check benchmark.check_search_path_benchmark + assert_equal $status 1 + cd - +} + +@test "steampipe check exitCode - with controls in error(running multiple benchmarks together)" { + cd $FUNCTIONALITY_TEST_MOD + run steampipe check benchmark.control_summary_benchmark benchmark.check_cache_benchmark + assert_equal $status 2 + cd - +} + +@test "steampipe check exitCode - runtime error(insufficient args)" { + cd $FUNCTIONALITY_TEST_MOD + run steampipe check + assert_equal $status 254 cd - }