mirror of
https://github.com/turbot/steampipe.git
synced 2025-12-25 03:00:48 -05:00
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:
103
cmd/check.go
103
cmd/check.go
@@ -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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
32
cmd/query.go
32
cmd/query.go
@@ -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))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
123
pkg/control/init_data.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
Reference in New Issue
Block a user