Validate checkout output and export formats before execution. Closes #2619. Add support for using sps as snapshot output/export format. Closes #2623

This commit is contained in:
kaidaguerre
2022-10-28 14:57:44 +01:00
committed by GitHub
parent 8045e2ed75
commit 0452bb0b81
14 changed files with 257 additions and 209 deletions

View File

@@ -14,14 +14,12 @@ import (
"github.com/turbot/steampipe/pkg/cmdconfig" "github.com/turbot/steampipe/pkg/cmdconfig"
"github.com/turbot/steampipe/pkg/constants" "github.com/turbot/steampipe/pkg/constants"
"github.com/turbot/steampipe/pkg/contexthelpers" "github.com/turbot/steampipe/pkg/contexthelpers"
"github.com/turbot/steampipe/pkg/control"
"github.com/turbot/steampipe/pkg/control/controldisplay" "github.com/turbot/steampipe/pkg/control/controldisplay"
"github.com/turbot/steampipe/pkg/control/controlexecute" "github.com/turbot/steampipe/pkg/control/controlexecute"
"github.com/turbot/steampipe/pkg/control/controlstatus" "github.com/turbot/steampipe/pkg/control/controlstatus"
"github.com/turbot/steampipe/pkg/display" "github.com/turbot/steampipe/pkg/display"
"github.com/turbot/steampipe/pkg/error_helpers" "github.com/turbot/steampipe/pkg/error_helpers"
"github.com/turbot/steampipe/pkg/initialisation"
"github.com/turbot/steampipe/pkg/interactive"
"github.com/turbot/steampipe/pkg/statushooks"
"github.com/turbot/steampipe/pkg/utils" "github.com/turbot/steampipe/pkg/utils"
"github.com/turbot/steampipe/pkg/workspace" "github.com/turbot/steampipe/pkg/workspace"
) )
@@ -91,7 +89,6 @@ You may specify one or more benchmarks or controls to run (separated by a space)
func runCheckCmd(cmd *cobra.Command, args []string) { func runCheckCmd(cmd *cobra.Command, args []string) {
utils.LogTime("runCheckCmd start") utils.LogTime("runCheckCmd start")
initData := &initialisation.InitData{}
// setup a cancel context and start cancel handler // setup a cancel context and start cancel handler
ctx, cancel := context.WithCancel(cmd.Context()) ctx, cancel := context.WithCancel(cmd.Context())
@@ -105,8 +102,6 @@ func runCheckCmd(cmd *cobra.Command, args []string) {
error_helpers.ShowError(ctx, helpers.ToError(r)) error_helpers.ShowError(ctx, helpers.ToError(r))
exitCode = constants.ExitCodeUnknownErrorPanic exitCode = constants.ExitCodeUnknownErrorPanic
} }
initData.Cleanup(ctx)
}() }()
// verify we have an argument // verify we have an argument
@@ -121,8 +116,10 @@ func runCheckCmd(cmd *cobra.Command, args []string) {
} }
// initialise // initialise
initData = initialiseCheck(ctx) initData := control.NewInitData(ctx)
error_helpers.FailOnError(initData.Result.Error) error_helpers.FailOnError(initData.Result.Error)
defer initData.Cleanup(ctx)
// if there is a usage warning we display it // if there is a usage warning we display it
initData.Result.DisplayMessages() initData.Result.DisplayMessages()
@@ -151,7 +148,7 @@ func runCheckCmd(cmd *cobra.Command, args []string) {
// execute controls synchronously (execute returns the number of failures) // execute controls synchronously (execute returns the number of failures)
failures += executionTree.Execute(ctx) failures += executionTree.Execute(ctx)
err = displayControlResults(ctx, executionTree) err = displayControlResults(ctx, executionTree, initData.OutputFormatter)
error_helpers.FailOnError(err) error_helpers.FailOnError(err)
exportArgs := viper.GetStringSlice(constants.ArgExport) exportArgs := viper.GetStringSlice(constants.ArgExport)
@@ -201,78 +198,6 @@ func validateCheckArgs(ctx context.Context, cmd *cobra.Command, args []string) b
return true return true
} }
func initialiseCheck(ctx context.Context) *initialisation.InitData {
statushooks.SetStatus(ctx, "Initializing...")
defer statushooks.Done(ctx)
// load the workspace
w, err := interactive.LoadWorkspacePromptingForVariables(ctx)
error_helpers.FailOnErrorWithMessage(err, "failed to load workspace")
initData := initialisation.NewInitData(w).Init(ctx, constants.InvokerCheck)
if initData.Result.Error != nil {
return initData
}
// control specific init
if !w.ModfileExists() {
initData.Result.Error = workspace.ErrorNoModDefinition
}
if viper.GetString(constants.ArgOutput) == constants.OutputFormatNone {
// set progress to false
viper.Set(constants.ArgProgress, false)
}
// set color schema
err = initialiseCheckColorScheme()
if err != nil {
initData.Result.Error = err
return initData
}
if len(initData.Workspace.GetResourceMaps().Controls) == 0 {
initData.Result.AddWarnings("no controls found in current workspace")
}
if err := controldisplay.EnsureTemplates(); err != nil {
initData.Result.Error = err
return initData
}
if len(viper.GetStringSlice(constants.ArgExport)) > 0 {
registerCheckExporters(initData)
}
return initData
}
// register exporters for each of the supported check formats
func registerCheckExporters(initData *initialisation.InitData) {
exporters, err := controldisplay.GetExporters()
error_helpers.FailOnErrorWithMessage(err, "failed to load exporters")
// register all exporters
initData.RegisterExporters(exporters...)
}
func initialiseCheckColorScheme() error {
theme := viper.GetString(constants.ArgTheme)
if !viper.GetBool(constants.ConfigKeyIsTerminalTTY) {
// enforce plain output for non-terminals
theme = "plain"
}
themeDef, ok := controldisplay.ColorSchemes[theme]
if !ok {
return fmt.Errorf("invalid theme '%s'", theme)
}
scheme, err := controldisplay.NewControlColorScheme(themeDef)
if err != nil {
return err
}
controldisplay.ControlColors = scheme
return nil
}
func printTiming(args []string, durations []time.Duration) { func printTiming(args []string, durations []time.Duration) {
headers := []string{"", "Duration"} headers := []string{"", "Duration"}
var rows [][]string var rows [][]string
@@ -292,13 +217,7 @@ func shouldPrintTiming() bool {
(outputFormat == constants.OutputFormatText || outputFormat == constants.OutputFormatBrief) (outputFormat == constants.OutputFormatText || outputFormat == constants.OutputFormatBrief)
} }
func displayControlResults(ctx context.Context, executionTree *controlexecute.ExecutionTree) error { func displayControlResults(ctx context.Context, executionTree *controlexecute.ExecutionTree, formatter controldisplay.Formatter) error {
output := viper.GetString(constants.ArgOutput)
formatter, err := parseOutputArg(output)
if err != nil {
fmt.Println(err)
return err
}
reader, err := formatter.Format(ctx, executionTree) reader, err := formatter.Format(ctx, executionTree)
if err != nil { if err != nil {
return err return err
@@ -306,13 +225,3 @@ func displayControlResults(ctx context.Context, executionTree *controlexecute.Ex
_, err = io.Copy(os.Stdout, reader) _, err = io.Copy(os.Stdout, reader)
return err return err
} }
// parseOutputArg parses the --output flag value and returns the Formatter that can format the data
func parseOutputArg(arg string) (formatter controldisplay.Formatter, err error) {
formatResolver, err := controldisplay.NewFormatResolver()
if err != nil {
return nil, err
}
return formatResolver.GetFormatter(arg)
}

View File

@@ -23,7 +23,6 @@ import (
"github.com/turbot/steampipe/pkg/error_helpers" "github.com/turbot/steampipe/pkg/error_helpers"
"github.com/turbot/steampipe/pkg/export" "github.com/turbot/steampipe/pkg/export"
"github.com/turbot/steampipe/pkg/initialisation" "github.com/turbot/steampipe/pkg/initialisation"
"github.com/turbot/steampipe/pkg/interactive"
"github.com/turbot/steampipe/pkg/statushooks" "github.com/turbot/steampipe/pkg/statushooks"
"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig" "github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
"github.com/turbot/steampipe/pkg/utils" "github.com/turbot/steampipe/pkg/utils"
@@ -86,7 +85,6 @@ func runDashboardCmd(cmd *cobra.Command, args []string) {
} }
} }
setExitCodeForDashboardError(err) setExitCodeForDashboardError(err)
}() }()
// first check whether a dashboard name has been passed as an arg // first check whether a dashboard name has been passed as an arg
@@ -103,7 +101,7 @@ func runDashboardCmd(cmd *cobra.Command, args []string) {
inputs, err := collectInputs() inputs, err := collectInputs()
error_helpers.FailOnError(err) error_helpers.FailOnError(err)
// run just this dashboard // run just this dashboard - this handles all initialisation
err = runSingleDashboard(dashboardCtx, dashboardName, inputs) err = runSingleDashboard(dashboardCtx, dashboardName, inputs)
error_helpers.FailOnError(err) error_helpers.FailOnError(err)
@@ -189,10 +187,10 @@ func validateDashboardArgs(ctx context.Context, args []string) (string, error) {
} }
} }
validOutputFormats := []string{constants.OutputFormatSnapshot, constants.OutputFormatNone} validOutputFormats := []string{constants.OutputFormatSnapshot, constants.OutputFormatSnapshotShort, constants.OutputFormatNone}
output := viper.GetString(constants.ArgOutput) output := viper.GetString(constants.ArgOutput)
if !helpers.StringSliceContains(validOutputFormats, output) { if !helpers.StringSliceContains(validOutputFormats, output) {
return "", fmt.Errorf("invalid output format '%s', must be one of %s", output, strings.Join(validOutputFormats, ",")) return "", fmt.Errorf("invalid output format: '%s', must be one of [%s]", output, strings.Join(validOutputFormats, ", "))
} }
return dashboardName, nil return dashboardName, nil
@@ -200,7 +198,7 @@ func validateDashboardArgs(ctx context.Context, args []string) (string, error) {
func displaySnapshot(snapshot *dashboardtypes.SteampipeSnapshot) { func displaySnapshot(snapshot *dashboardtypes.SteampipeSnapshot) {
switch viper.GetString(constants.ArgOutput) { switch viper.GetString(constants.ArgOutput) {
case constants.OutputFormatSnapshot: case constants.OutputFormatSnapshot, constants.OutputFormatSnapshotShort:
// just display result // just display result
snapshotText, err := json.MarshalIndent(snapshot, "", " ") snapshotText, err := json.MarshalIndent(snapshot, "", " ")
error_helpers.FailOnError(err) error_helpers.FailOnError(err)
@@ -210,27 +208,40 @@ func displaySnapshot(snapshot *dashboardtypes.SteampipeSnapshot) {
func initDashboard(ctx context.Context) *initialisation.InitData { func initDashboard(ctx context.Context) *initialisation.InitData {
dashboardserver.OutputWait(ctx, "Loading Workspace") dashboardserver.OutputWait(ctx, "Loading Workspace")
w, err := interactive.LoadWorkspacePromptingForVariables(ctx)
if err != nil {
return initialisation.NewErrorInitData(fmt.Errorf("failed to load workspace: %s", err.Error()))
}
// initialise // initialise
initData := getInitData(ctx, w) initData := getInitData(ctx)
if initData.Result.Error != nil {
return initData
}
// there must be a mod-file // there must be a mod-file
if !w.ModfileExists() { if !initData.Workspace.ModfileExists() {
initData.Result.Error = workspace.ErrorNoModDefinition initData.Result.Error = workspace.ErrorNoModDefinition
} }
return initData return initData
} }
func getInitData(ctx context.Context, w *workspace.Workspace) *initialisation.InitData { func getInitData(ctx context.Context) *initialisation.InitData {
initData := initialisation.NewInitData(w). w, err := workspace.LoadWorkspacePromptingForVariables(ctx)
RegisterExporters(dashboardExporters()...). if err != nil {
return initialisation.NewErrorInitData(fmt.Errorf("failed to load workspace: %s", err.Error()))
}
i := initialisation.NewInitData(w).
Init(ctx, constants.InvokerDashboard) Init(ctx, constants.InvokerDashboard)
return initData
if len(viper.GetStringSlice(constants.ArgExport)) > 0 {
i.RegisterExporters(dashboardExporters()...)
// validate required export formats
if err := i.ExportManager.ValidateExportFormat(viper.GetStringSlice(constants.ArgExport)); err != nil {
i.Result.Error = err
return i
}
}
return i
} }
func dashboardExporters() []export.Exporter { func dashboardExporters() []export.Exporter {
@@ -238,22 +249,17 @@ func dashboardExporters() []export.Exporter {
} }
func runSingleDashboard(ctx context.Context, targetName string, inputs map[string]interface{}) error { func runSingleDashboard(ctx context.Context, targetName string, inputs map[string]interface{}) error {
w, err := interactive.LoadWorkspacePromptingForVariables(ctx) initData := getInitData(ctx)
error_helpers.FailOnErrorWithMessage(err, "failed to load workspace")
// targetName must be a named resource
// parse the name to verify
if err := verifyNamedResource(targetName, w); err != nil {
return err
}
initData := getInitData(ctx, w)
// shutdown the service on exit // shutdown the service on exit
defer initData.Cleanup(ctx) defer initData.Cleanup(ctx)
if err := initData.Result.Error; err != nil { if err := initData.Result.Error; err != nil {
return initData.Result.Error return initData.Result.Error
} }
// targetName must be a named resource
// parse the name to verify
if err := verifyNamedResource(targetName, initData.Workspace); err != nil {
return err
}
// if there is a usage warning we display it // if there is a usage warning we display it
initData.Result.DisplayMessages() initData.Result.DisplayMessages()

View File

@@ -20,7 +20,6 @@ import (
"github.com/turbot/steampipe/pkg/dashboard/dashboardtypes" "github.com/turbot/steampipe/pkg/dashboard/dashboardtypes"
"github.com/turbot/steampipe/pkg/display" "github.com/turbot/steampipe/pkg/display"
"github.com/turbot/steampipe/pkg/error_helpers" "github.com/turbot/steampipe/pkg/error_helpers"
"github.com/turbot/steampipe/pkg/interactive"
"github.com/turbot/steampipe/pkg/query" "github.com/turbot/steampipe/pkg/query"
"github.com/turbot/steampipe/pkg/query/queryexecute" "github.com/turbot/steampipe/pkg/query/queryexecute"
"github.com/turbot/steampipe/pkg/query/queryresult" "github.com/turbot/steampipe/pkg/query/queryresult"
@@ -125,15 +124,10 @@ func runQueryCmd(cmd *cobra.Command, args []string) {
// set config to indicate whether we are running an interactive query // set config to indicate whether we are running an interactive query
viper.Set(constants.ConfigKeyInteractive, interactiveMode) viper.Set(constants.ConfigKeyInteractive, interactiveMode)
// load the workspace
w, err := interactive.LoadWorkspacePromptingForVariables(ctx)
error_helpers.FailOnErrorWithMessage(err, "failed to load workspace")
// so we have loaded a workspace - be sure to close it
defer w.Close()
// start the initializer // start the initializer
initData := query.NewInitData(ctx, w, args) initData := query.NewInitData(ctx, args)
error_helpers.FailOnError(initData.Result.Error)
defer initData.Cleanup(ctx)
switch { switch {
case interactiveMode: case interactiveMode:
@@ -141,7 +135,7 @@ func runQueryCmd(cmd *cobra.Command, args []string) {
case snapshotRequired(): case snapshotRequired():
// if we are either outputting snapshot format, or sharing the results as a snapshot, execute the query // if we are either outputting snapshot format, or sharing the results as a snapshot, execute the query
// as a dashboard // as a dashboard
exitCode = executeSnapshotQuery(initData, w, ctx) exitCode = executeSnapshotQuery(initData, ctx)
default: default:
// NOTE: disable any status updates - we do not want 'loading' output from any queries // NOTE: disable any status updates - we do not want 'loading' output from any queries
ctx = statushooks.DisableStatusHooks(ctx) ctx = statushooks.DisableStatusHooks(ctx)
@@ -163,19 +157,16 @@ func validateQueryArgs(ctx context.Context, args []string) error {
return err return err
} }
validOutputFormats := []string{constants.OutputFormatLine, constants.OutputFormatCSV, constants.OutputFormatTable, constants.OutputFormatJSON, constants.OutputFormatSnapshot, constants.OutputFormatNone} validOutputFormats := []string{constants.OutputFormatLine, constants.OutputFormatCSV, constants.OutputFormatTable, constants.OutputFormatJSON, constants.OutputFormatSnapshot, constants.OutputFormatSnapshotShort, constants.OutputFormatNone}
output := viper.GetString(constants.ArgOutput) output := viper.GetString(constants.ArgOutput)
if !helpers.StringSliceContains(validOutputFormats, output) { if !helpers.StringSliceContains(validOutputFormats, output) {
return fmt.Errorf("invalid output format '%s', must be one of %s", output, strings.Join(validOutputFormats, ",")) return fmt.Errorf("invalid output format: '%s', must be one of [%s]", output, strings.Join(validOutputFormats, ", "))
} }
return nil return nil
} }
func executeSnapshotQuery(initData *query.InitData, w *workspace.Workspace, ctx context.Context) int { func executeSnapshotQuery(initData *query.InitData, ctx context.Context) int {
// ensure we close client
defer initData.Cleanup(ctx)
// start cancel handler to intercept interrupts and cancel the context // start cancel handler to intercept interrupts and cancel the context
// NOTE: use the initData Cancel function to ensure any initialisation is cancelled if needed // NOTE: use the initData Cancel function to ensure any initialisation is cancelled if needed
contexthelpers.StartCancelHandler(initData.Cancel) contexthelpers.StartCancelHandler(initData.Cancel)
@@ -197,7 +188,7 @@ func executeSnapshotQuery(initData *query.InitData, w *workspace.Workspace, ctx
// this is to allow us to use existing dashboard execution code // this is to allow us to use existing dashboard execution code
// build query name and title // build query name and title
targetName := ensureQueryResource(name, query, i, len(queryNames), w) targetName := ensureQueryResource(name, query, i, len(queryNames), initData.Workspace)
// we need to pass the embedded initData to GenerateSnapshot // we need to pass the embedded initData to GenerateSnapshot
baseInitData := &initData.InitData baseInitData := &initData.InitData
@@ -210,7 +201,7 @@ func executeSnapshotQuery(initData *query.InitData, w *workspace.Workspace, ctx
switch viper.GetString(constants.ArgOutput) { switch viper.GetString(constants.ArgOutput) {
case constants.OutputFormatNone: case constants.OutputFormatNone:
// do nothing // do nothing
case constants.OutputFormatSnapshot: case constants.OutputFormatSnapshot, constants.OutputFormatSnapshotShort:
// if the format is snapshot, just dump it out // if the format is snapshot, just dump it out
jsonOutput, err := json.MarshalIndent(snap, "", " ") jsonOutput, err := json.MarshalIndent(snap, "", " ")
if err != nil { if err != nil {
@@ -301,16 +292,17 @@ func ensureQueryResource(name string, query string, queryIdx, queryCount int, w
} }
func snapshotRequired() bool { func snapshotRequired() bool {
SnapshotFormatNames := []string{constants.OutputFormatSnapshot, constants.OutputFormatSnapshotShort}
// if a snapshot exporter is specified return true // if a snapshot exporter is specified return true
for _, e := range viper.GetStringSlice(constants.ArgExport) { for _, e := range viper.GetStringSlice(constants.ArgExport) {
if e == constants.OutputFormatSnapshot || path.Ext(e) == constants.SnapshotExtension { if helpers.StringSliceContains(SnapshotFormatNames, e) || path.Ext(e) == constants.SnapshotExtension {
return true return true
} }
} }
// if share/snapshot args are set or output is snapshot, return true // if share/snapshot args are set or output is snapshot, return true
return viper.IsSet(constants.ArgShare) || return viper.IsSet(constants.ArgShare) ||
viper.IsSet(constants.ArgSnapshot) || viper.IsSet(constants.ArgSnapshot) ||
viper.GetString(constants.ArgOutput) == constants.OutputFormatSnapshot helpers.StringSliceContains(SnapshotFormatNames, viper.GetString(constants.ArgOutput))
} }

View File

@@ -1,12 +1,13 @@
package constants package constants
const ( const (
OutputFormatCSV = "csv" OutputFormatCSV = "csv"
OutputFormatJSON = "json" OutputFormatJSON = "json"
OutputFormatTable = "table" OutputFormatTable = "table"
OutputFormatLine = "line" OutputFormatLine = "line"
OutputFormatNone = "none" OutputFormatNone = "none"
OutputFormatText = "text" OutputFormatText = "text"
OutputFormatBrief = "brief" OutputFormatBrief = "brief"
OutputFormatSnapshot = "snapshot" OutputFormatSnapshot = "snapshot"
OutputFormatSnapshotShort = "sps"
) )

View File

@@ -56,7 +56,7 @@ func (r *FormatResolver) GetFormatter(arg string) (Formatter, error) {
return formatter, nil return formatter, nil
} }
return nil, fmt.Errorf("could not resolve formatter for %s", arg) return nil, fmt.Errorf(" invalid output format: '%s'", arg)
} }
func (r *FormatResolver) registerFormatter(f Formatter) error { func (r *FormatResolver) registerFormatter(f Formatter) error {

View File

@@ -40,5 +40,5 @@ func (f SnapshotFormatter) Name() string {
} }
func (f *SnapshotFormatter) Alias() string { func (f *SnapshotFormatter) Alias() string {
return "" return "sps"
} }

123
pkg/control/init_data.go Normal file
View File

@@ -0,0 +1,123 @@
package control
import (
"context"
"fmt"
"github.com/turbot/steampipe/pkg/control/controldisplay"
"github.com/turbot/steampipe/pkg/error_helpers"
"github.com/turbot/steampipe/pkg/statushooks"
"github.com/spf13/viper"
"github.com/turbot/steampipe/pkg/constants"
"github.com/turbot/steampipe/pkg/initialisation"
"github.com/turbot/steampipe/pkg/workspace"
)
type InitData struct {
initialisation.InitData
OutputFormatter controldisplay.Formatter
}
// NewInitData returns a new InitData object
// It also starts an asynchronous population of the object
// InitData.Done closes after asynchronous initialization completes
func NewInitData(ctx context.Context) *InitData {
statushooks.SetStatus(ctx, "Initializing...")
defer statushooks.Done(ctx)
// load the workspace
w, err := workspace.LoadWorkspacePromptingForVariables(ctx)
if err != nil {
return &InitData{
InitData: *initialisation.NewErrorInitData(fmt.Errorf("failed to load workspace: %s", err.Error())),
}
}
i := &InitData{
InitData: *initialisation.NewInitData(w).Init(ctx, constants.InvokerCheck),
}
if i.Result.Error != nil {
return i
}
if !w.ModfileExists() {
i.Result.Error = workspace.ErrorNoModDefinition
}
if viper.GetString(constants.ArgOutput) == constants.OutputFormatNone {
// set progress to false
viper.Set(constants.ArgProgress, false)
}
// set color schema
err = initialiseCheckColorScheme()
if err != nil {
i.Result.Error = err
return i
}
if len(i.Workspace.GetResourceMaps().Controls) == 0 {
i.Result.AddWarnings("no controls found in current workspace")
}
if err := controldisplay.EnsureTemplates(); err != nil {
i.Result.Error = err
return i
}
if len(viper.GetStringSlice(constants.ArgExport)) > 0 {
i.registerCheckExporters()
// validate required export formats
if err := i.ExportManager.ValidateExportFormat(viper.GetStringSlice(constants.ArgExport)); err != nil {
i.Result.Error = err
return i
}
}
output := viper.GetString(constants.ArgOutput)
formatter, err := parseOutputArg(output)
if err != nil {
i.Result.Error = err
return i
}
i.OutputFormatter = formatter
return i
}
// register exporters for each of the supported check formats
func (initData *InitData) registerCheckExporters() {
exporters, err := controldisplay.GetExporters()
error_helpers.FailOnErrorWithMessage(err, "failed to load exporters")
// register all exporters
initData.RegisterExporters(exporters...)
}
// parseOutputArg parses the --output flag value and returns the Formatter that can format the data
func parseOutputArg(arg string) (formatter controldisplay.Formatter, err error) {
formatResolver, err := controldisplay.NewFormatResolver()
if err != nil {
return nil, err
}
return formatResolver.GetFormatter(arg)
}
func initialiseCheckColorScheme() error {
theme := viper.GetString(constants.ArgTheme)
if !viper.GetBool(constants.ConfigKeyIsTerminalTTY) {
// enforce plain output for non-terminals
theme = "plain"
}
themeDef, ok := controldisplay.ColorSchemes[theme]
if !ok {
return fmt.Errorf("invalid theme '%s'", theme)
}
scheme, err := controldisplay.NewControlColorScheme(themeDef)
if err != nil {
return err
}
controldisplay.ControlColors = scheme
return nil
}

View File

@@ -3,10 +3,12 @@ package export
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/turbot/steampipe/pkg/error_helpers"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"path" "path"
"strings" "strings"
"github.com/turbot/steampipe/pkg/error_helpers"
"github.com/turbot/steampipe/pkg/utils"
) )
type Manager struct { type Manager struct {
@@ -148,3 +150,17 @@ func (m *Manager) DoExport(ctx context.Context, targetName string, source Export
} }
return error_helpers.CombineErrors(errors...) return error_helpers.CombineErrors(errors...)
} }
func (m *Manager) ValidateExportFormat(exports []string) error {
var invalidFormats []string
for _, export := range exports {
if _, err := m.getExportTarget(export, "dummy_target_name"); err != nil {
invalidFormats = append(invalidFormats, export)
}
}
if invalidCount := len(invalidFormats); invalidCount > 0 {
return fmt.Errorf("invalid export %s: '%s'", utils.Pluralize("format", invalidCount), strings.Join(invalidFormats, "','"))
}
return nil
}

View File

@@ -33,6 +33,10 @@ func (e *SnapshotExporter) FileExtension() string {
return constants.SnapshotExtension return constants.SnapshotExtension
} }
func (e SnapshotExporter) Name() string { func (e *SnapshotExporter) Name() string {
return constants.OutputFormatSnapshot return constants.OutputFormatSnapshot
} }
func (*SnapshotExporter) Alias() string {
return "sps"
}

View File

@@ -171,7 +171,6 @@ func (i *InitData) Cleanup(ctx context.Context) {
if i.Client != nil { if i.Client != nil {
i.Client.Close(ctx) i.Client.Close(ctx)
} }
if i.ShutdownTelemetry != nil { if i.ShutdownTelemetry != nil {
i.ShutdownTelemetry() i.ShutdownTelemetry()
} }

View File

@@ -1,39 +0,0 @@
package interactive
import (
"context"
"log"
"github.com/spf13/viper"
"github.com/turbot/steampipe/pkg/constants"
"github.com/turbot/steampipe/pkg/statushooks"
"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
"github.com/turbot/steampipe/pkg/workspace"
)
func LoadWorkspacePromptingForVariables(ctx context.Context) (*workspace.Workspace, error) {
workspacePath := viper.GetString(constants.ArgModLocation)
w, err := workspace.Load(ctx, workspacePath)
if err == nil {
return w, nil
}
missingVariablesError, ok := err.(modconfig.MissingVariableError)
// if there was an error which is NOT a MissingVariableError, return it
if !ok {
return nil, err
}
// if interactive input is disabled, return the missing variables error
if !viper.GetBool(constants.ArgInput) {
return nil, missingVariablesError
}
// so we have missing variables - prompt for them
// first hide spinner if it is there
statushooks.Done(ctx)
if err := PromptForMissingVariables(ctx, missingVariablesError.MissingVariables, workspacePath); err != nil {
log.Printf("[TRACE] Interactive variables prompting returned error %v", err)
return nil, err
}
// ok we should have all variables now - reload workspace
return workspace.Load(ctx, workspacePath)
}

View File

@@ -2,12 +2,11 @@ package query
import ( import (
"context" "context"
"fmt"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/turbot/steampipe/pkg/constants" "github.com/turbot/steampipe/pkg/constants"
"github.com/turbot/steampipe/pkg/export" "github.com/turbot/steampipe/pkg/export"
"github.com/turbot/steampipe/pkg/initialisation" "github.com/turbot/steampipe/pkg/initialisation"
"github.com/turbot/steampipe/pkg/statushooks"
"github.com/turbot/steampipe/pkg/workspace" "github.com/turbot/steampipe/pkg/workspace"
) )
@@ -22,13 +21,29 @@ type InitData struct {
// NewInitData returns a new InitData object // NewInitData returns a new InitData object
// It also starts an asynchronous population of the object // It also starts an asynchronous population of the object
// InitData.Done closes after asynchronous initialization completes // InitData.Done closes after asynchronous initialization completes
func NewInitData(ctx context.Context, w *workspace.Workspace, args []string) *InitData { func NewInitData(ctx context.Context, args []string) *InitData {
// load the workspace
w, err := workspace.LoadWorkspacePromptingForVariables(ctx)
if err != nil {
return &InitData{
InitData: *initialisation.NewErrorInitData(fmt.Errorf("failed to load workspace: %s", err.Error())),
}
}
i := &InitData{ i := &InitData{
InitData: *initialisation.NewInitData(w), InitData: *initialisation.NewInitData(w),
Loaded: make(chan struct{}), Loaded: make(chan struct{}),
} }
i.RegisterExporters(queryExporters()...) if len(viper.GetStringSlice(constants.ArgExport)) > 0 {
i.RegisterExporters(queryExporters()...)
// validate required export formats
if err := i.ExportManager.ValidateExportFormat(viper.GetStringSlice(constants.ArgExport)); err != nil {
i.Result.Error = err
return i
}
}
go i.init(ctx, w, args) go i.init(ctx, w, args)
@@ -90,8 +105,4 @@ func (i *InitData) init(ctx context.Context, w *workspace.Workspace, args []stri
// and call base init // and call base init
i.InitData.Init(ctx, constants.InvokerQuery) i.InitData.Init(ctx, constants.InvokerQuery)
if len(args) > 0 {
// disable status hooks for batch mode
ctx = statushooks.DisableStatusHooks(ctx)
}
} }

View File

@@ -34,9 +34,6 @@ func RunInteractiveSession(ctx context.Context, initData *query.InitData) {
} }
func RunBatchSession(ctx context.Context, initData *query.InitData) int { func RunBatchSession(ctx context.Context, initData *query.InitData) int {
// ensure we close client
defer initData.Cleanup(ctx)
// start cancel handler to intercept interrupts and cancel the context // start cancel handler to intercept interrupts and cancel the context
// NOTE: use the initData Cancel function to ensure any initialisation is cancelled if needed // NOTE: use the initData Cancel function to ensure any initialisation is cancelled if needed
contexthelpers.StartCancelHandler(initData.Cancel) contexthelpers.StartCancelHandler(initData.Cancel)

View File

@@ -1,17 +1,46 @@
package interactive package workspace
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/turbot/steampipe/pkg/constants" "github.com/turbot/steampipe/pkg/constants"
"github.com/turbot/steampipe/pkg/statushooks"
"github.com/turbot/steampipe/pkg/steampipeconfig/inputvars" "github.com/turbot/steampipe/pkg/steampipeconfig/inputvars"
"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig" "github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
) )
func PromptForMissingVariables(ctx context.Context, missingVariables []*modconfig.Variable, workspacePath string) error { func LoadWorkspacePromptingForVariables(ctx context.Context) (*Workspace, error) {
workspacePath := viper.GetString(constants.ArgModLocation)
w, err := Load(ctx, workspacePath)
if err == nil {
return w, nil
}
missingVariablesError, ok := err.(modconfig.MissingVariableError)
// if there was an error which is NOT a MissingVariableError, return it
if !ok {
return nil, err
}
// if interactive input is disabled, return the missing variables error
if !viper.GetBool(constants.ArgInput) {
return nil, missingVariablesError
}
// so we have missing variables - prompt for them
// first hide spinner if it is there
statushooks.Done(ctx)
if err := promptForMissingVariables(ctx, missingVariablesError.MissingVariables, workspacePath); err != nil {
log.Printf("[TRACE] Interactive variables prompting returned error %v", err)
return nil, err
}
// ok we should have all variables now - reload workspace
return Load(ctx, workspacePath)
}
func promptForMissingVariables(ctx context.Context, missingVariables []*modconfig.Variable, workspacePath string) error {
fmt.Println() fmt.Println()
fmt.Println("Variables defined with no value set.") fmt.Println("Variables defined with no value set.")
for _, v := range missingVariables { for _, v := range missingVariables {