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 }