github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/control/init_data.go (about)

     1  package control
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/url"
     7  	"strings"
     8  
     9  	"github.com/spf13/viper"
    10  	"github.com/turbot/steampipe/pkg/constants"
    11  	"github.com/turbot/steampipe/pkg/control/controldisplay"
    12  	"github.com/turbot/steampipe/pkg/error_helpers"
    13  	"github.com/turbot/steampipe/pkg/initialisation"
    14  	"github.com/turbot/steampipe/pkg/statushooks"
    15  	"github.com/turbot/steampipe/pkg/workspace"
    16  )
    17  
    18  type InitData struct {
    19  	initialisation.InitData
    20  	OutputFormatter          controldisplay.Formatter
    21  	ControlFilterWhereClause string
    22  }
    23  
    24  // NewInitData returns a new InitData object
    25  // It also starts an asynchronous population of the object
    26  // InitData.Done closes after asynchronous initialization completes
    27  func NewInitData(ctx context.Context) *InitData {
    28  	// create InitData, but do not initialize yet, since 'viper' is not completely setup
    29  	i := &InitData{
    30  		InitData: *initialisation.NewInitData(),
    31  	}
    32  
    33  	statushooks.SetStatus(ctx, "Loading workspace")
    34  
    35  	// load the workspace
    36  	w, errAndWarnings := workspace.LoadWorkspacePromptingForVariables(ctx)
    37  	if errAndWarnings.GetError() != nil {
    38  		return &InitData{
    39  			InitData: *initialisation.NewErrorInitData(fmt.Errorf("failed to load workspace: %s", error_helpers.HandleCancelError(errAndWarnings.GetError()).Error())),
    40  		}
    41  	}
    42  
    43  	statushooks.SetStatus(ctx, "Initialising...")
    44  
    45  	// disable status hooks for any subsequent initialisation
    46  	ctx = statushooks.DisableStatusHooks(ctx)
    47  
    48  	i.Workspace = w
    49  	i.Result.AddWarnings(errAndWarnings.Warnings...)
    50  	if !w.ModfileExists() {
    51  		i.Result.Error = workspace.ErrorNoModDefinition
    52  	}
    53  
    54  	if viper.GetString(constants.ArgOutput) == constants.OutputFormatNone {
    55  		// set progress to false
    56  		viper.Set(constants.ArgProgress, false)
    57  	}
    58  	// set color schema
    59  	err := initialiseCheckColorScheme()
    60  	if err != nil {
    61  		i.Result.Error = err
    62  		return i
    63  	}
    64  
    65  	if len(w.GetResourceMaps().Controls)+len(w.GetResourceMaps().Benchmarks) == 0 {
    66  		i.Result.AddWarnings("no controls or benchmarks found in current workspace")
    67  	}
    68  
    69  	if err := controldisplay.EnsureTemplates(); err != nil {
    70  		i.Result.Error = err
    71  		return i
    72  	}
    73  
    74  	if len(viper.GetStringSlice(constants.ArgExport)) > 0 {
    75  		i.registerCheckExporters(ctx)
    76  		// validate required export formats
    77  		if err := i.ExportManager.ValidateExportFormat(viper.GetStringSlice(constants.ArgExport)); err != nil {
    78  			i.Result.Error = err
    79  			return i
    80  		}
    81  	}
    82  
    83  	output := viper.GetString(constants.ArgOutput)
    84  	formatter, err := parseOutputArg(ctx, output)
    85  	if err != nil {
    86  		i.Result.Error = err
    87  		return i
    88  	}
    89  	i.OutputFormatter = formatter
    90  
    91  	i.setControlFilterClause()
    92  
    93  	// initialize
    94  	i.InitData.Init(ctx, constants.InvokerCheck)
    95  
    96  	return i
    97  }
    98  
    99  func (i *InitData) setControlFilterClause() {
   100  	if viper.IsSet(constants.ArgTag) {
   101  		// if '--tag' args were used, derive the whereClause from them
   102  		tags := viper.GetStringSlice(constants.ArgTag)
   103  		i.ControlFilterWhereClause = generateWhereClauseFromTags(tags)
   104  	} else if viper.IsSet(constants.ArgWhere) {
   105  		// if a 'where' arg was used, execute this sql to get a list of  control names
   106  		// use this list to build a name map used to determine whether to run a particular control
   107  		i.ControlFilterWhereClause = viper.GetString(constants.ArgWhere)
   108  	}
   109  
   110  	// if we derived or were passed a where clause, run the filter
   111  	if len(i.ControlFilterWhereClause) > 0 {
   112  		// if we have a control filter where clause, we must create the control introspection tables
   113  		viper.Set(constants.ArgIntrospection, constants.IntrospectionControl)
   114  	}
   115  }
   116  
   117  func generateWhereClauseFromTags(tags []string) string {
   118  	whereMap := map[string][]string{}
   119  
   120  	// 'tags' should be KV Pairs of the form: 'benchmark=pic' or 'cis_level=1'
   121  	for _, tag := range tags {
   122  		value, _ := url.ParseQuery(tag)
   123  		for k, v := range value {
   124  			if _, found := whereMap[k]; !found {
   125  				whereMap[k] = []string{}
   126  			}
   127  			whereMap[k] = append(whereMap[k], v...)
   128  		}
   129  	}
   130  	whereComponents := []string{}
   131  	for key, values := range whereMap {
   132  		thisComponent := []string{}
   133  		for _, x := range values {
   134  			if len(x) == 0 {
   135  				// ignore
   136  				continue
   137  			}
   138  			thisComponent = append(thisComponent, fmt.Sprintf("tags->>'%s'='%s'", key, x))
   139  		}
   140  		whereComponents = append(whereComponents, fmt.Sprintf("(%s)", strings.Join(thisComponent, " OR ")))
   141  	}
   142  
   143  	return strings.Join(whereComponents, " AND ")
   144  }
   145  
   146  // register exporters for each of the supported check formats
   147  func (i *InitData) registerCheckExporters(ctx context.Context) {
   148  	exporters, err := controldisplay.GetExporters(ctx)
   149  	error_helpers.FailOnErrorWithMessage(err, "failed to load exporters")
   150  
   151  	// register all exporters
   152  	i.RegisterExporters(exporters...)
   153  }
   154  
   155  // parseOutputArg parses the --output flag value and returns the Formatter that can format the data
   156  func parseOutputArg(ctx context.Context, arg string) (formatter controldisplay.Formatter, err error) {
   157  	formatResolver, err := controldisplay.NewFormatResolver(ctx)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	return formatResolver.GetFormatter(arg)
   163  }
   164  
   165  func initialiseCheckColorScheme() error {
   166  	theme := viper.GetString(constants.ArgTheme)
   167  	if !viper.GetBool(constants.ConfigKeyIsTerminalTTY) {
   168  		// enforce plain output for non-terminals
   169  		theme = "plain"
   170  	}
   171  	themeDef, ok := controldisplay.ColorSchemes[theme]
   172  	if !ok {
   173  		return fmt.Errorf("invalid theme '%s'", theme)
   174  	}
   175  	scheme, err := controldisplay.NewControlColorScheme(themeDef)
   176  	if err != nil {
   177  		return err
   178  	}
   179  	controldisplay.ControlColors = scheme
   180  	return nil
   181  }