github.com/crowdsecurity/crowdsec@v1.6.1/cmd/crowdsec-cli/main.go (about) 1 package main 2 3 import ( 4 "os" 5 "slices" 6 "time" 7 8 "github.com/fatih/color" 9 cc "github.com/ivanpirog/coloredcobra" 10 log "github.com/sirupsen/logrus" 11 "github.com/spf13/cobra" 12 13 "github.com/crowdsecurity/crowdsec/pkg/csconfig" 14 "github.com/crowdsecurity/crowdsec/pkg/database" 15 "github.com/crowdsecurity/crowdsec/pkg/fflag" 16 ) 17 18 var ConfigFilePath string 19 var csConfig *csconfig.Config 20 var dbClient *database.Client 21 22 type configGetter func() *csconfig.Config 23 24 var mergedConfig string 25 26 type cliRoot struct { 27 logTrace bool 28 logDebug bool 29 logInfo bool 30 logWarn bool 31 logErr bool 32 outputColor string 33 outputFormat string 34 // flagBranch overrides the value in csConfig.Cscli.HubBranch 35 flagBranch string 36 } 37 38 func newCliRoot() *cliRoot { 39 return &cliRoot{} 40 } 41 42 // cfg() is a helper function to get the configuration loaded from config.yaml, 43 // we pass it to subcommands because the file is not read until the Execute() call 44 func (cli *cliRoot) cfg() *csconfig.Config { 45 return csConfig 46 } 47 48 // wantedLogLevel returns the log level requested in the command line flags. 49 func (cli *cliRoot) wantedLogLevel() log.Level { 50 switch { 51 case cli.logTrace: 52 return log.TraceLevel 53 case cli.logDebug: 54 return log.DebugLevel 55 case cli.logInfo: 56 return log.InfoLevel 57 case cli.logWarn: 58 return log.WarnLevel 59 case cli.logErr: 60 return log.ErrorLevel 61 default: 62 return log.InfoLevel 63 } 64 } 65 66 // loadConfigFor loads the configuration file for the given sub-command. 67 // If the sub-command does not need it, it returns a default configuration. 68 func loadConfigFor(command string) (*csconfig.Config, string, error) { 69 noNeedConfig := []string{ 70 "doc", 71 "help", 72 "completion", 73 "version", 74 "hubtest", 75 } 76 77 if !slices.Contains(noNeedConfig, command) { 78 log.Debugf("Using %s as configuration file", ConfigFilePath) 79 80 config, merged, err := csconfig.NewConfig(ConfigFilePath, false, false, true) 81 if err != nil { 82 return nil, "", err 83 } 84 85 return config, merged, nil 86 } 87 88 return csconfig.NewDefaultConfig(), "", nil 89 } 90 91 // initialize is called before the subcommand is executed. 92 func (cli *cliRoot) initialize() { 93 var err error 94 95 log.SetLevel(cli.wantedLogLevel()) 96 97 csConfig, mergedConfig, err = loadConfigFor(os.Args[1]) 98 if err != nil { 99 log.Fatal(err) 100 } 101 102 // recap of the enabled feature flags, because logging 103 // was not enabled when we set them from envvars 104 if fflist := csconfig.ListFeatureFlags(); fflist != "" { 105 log.Debugf("Enabled feature flags: %s", fflist) 106 } 107 108 if cli.flagBranch != "" { 109 csConfig.Cscli.HubBranch = cli.flagBranch 110 } 111 112 if cli.outputFormat != "" { 113 csConfig.Cscli.Output = cli.outputFormat 114 } 115 116 if csConfig.Cscli.Output == "" { 117 csConfig.Cscli.Output = "human" 118 } 119 120 if csConfig.Cscli.Output != "human" && csConfig.Cscli.Output != "json" && csConfig.Cscli.Output != "raw" { 121 log.Fatalf("output format '%s' not supported: must be one of human, json, raw", csConfig.Cscli.Output) 122 } 123 124 if csConfig.Cscli.Output == "json" { 125 log.SetFormatter(&log.JSONFormatter{}) 126 log.SetLevel(log.ErrorLevel) 127 } else if csConfig.Cscli.Output == "raw" { 128 log.SetLevel(log.ErrorLevel) 129 } 130 131 if cli.outputColor != "" { 132 csConfig.Cscli.Color = cli.outputColor 133 134 if cli.outputColor != "yes" && cli.outputColor != "no" && cli.outputColor != "auto" { 135 log.Fatalf("output color %s unknown", cli.outputColor) 136 } 137 } 138 } 139 140 // list of valid subcommands for the shell completion 141 var validArgs = []string{ 142 "alerts", "appsec-configs", "appsec-rules", "bouncers", "capi", "collections", 143 "completion", "config", "console", "contexts", "dashboard", "decisions", "explain", 144 "hub", "hubtest", "lapi", "machines", "metrics", "notifications", "parsers", 145 "postoverflows", "scenarios", "simulation", "support", "version", 146 } 147 148 func (cli *cliRoot) colorize(cmd *cobra.Command) { 149 cc.Init(&cc.Config{ 150 RootCmd: cmd, 151 Headings: cc.Yellow, 152 Commands: cc.Green + cc.Bold, 153 CmdShortDescr: cc.Cyan, 154 Example: cc.Italic, 155 ExecName: cc.Bold, 156 Aliases: cc.Bold + cc.Italic, 157 FlagsDataType: cc.White, 158 Flags: cc.Green, 159 FlagsDescr: cc.Cyan, 160 NoExtraNewlines: true, 161 NoBottomNewline: true, 162 }) 163 cmd.SetOut(color.Output) 164 } 165 166 func (cli *cliRoot) NewCommand() *cobra.Command { 167 // set the formatter asap and worry about level later 168 logFormatter := &log.TextFormatter{TimestampFormat: time.RFC3339, FullTimestamp: true} 169 log.SetFormatter(logFormatter) 170 171 if err := fflag.RegisterAllFeatures(); err != nil { 172 log.Fatalf("failed to register features: %s", err) 173 } 174 175 if err := csconfig.LoadFeatureFlagsEnv(log.StandardLogger()); err != nil { 176 log.Fatalf("failed to set feature flags from env: %s", err) 177 } 178 179 cmd := &cobra.Command{ 180 Use: "cscli", 181 Short: "cscli allows you to manage crowdsec", 182 Long: `cscli is the main command to interact with your crowdsec service, scenarios & db. 183 It is meant to allow you to manage bans, parsers/scenarios/etc, api and generally manage you crowdsec setup.`, 184 ValidArgs: validArgs, 185 DisableAutoGenTag: true, 186 SilenceErrors: true, 187 SilenceUsage: true, 188 /*TBD examples*/ 189 } 190 191 cli.colorize(cmd) 192 193 /*don't sort flags so we can enforce order*/ 194 cmd.Flags().SortFlags = false 195 196 pflags := cmd.PersistentFlags() 197 pflags.SortFlags = false 198 199 pflags.StringVarP(&ConfigFilePath, "config", "c", csconfig.DefaultConfigPath("config.yaml"), "path to crowdsec config file") 200 pflags.StringVarP(&cli.outputFormat, "output", "o", "", "Output format: human, json, raw") 201 pflags.StringVarP(&cli.outputColor, "color", "", "auto", "Output color: yes, no, auto") 202 pflags.BoolVar(&cli.logDebug, "debug", false, "Set logging to debug") 203 pflags.BoolVar(&cli.logInfo, "info", false, "Set logging to info") 204 pflags.BoolVar(&cli.logWarn, "warning", false, "Set logging to warning") 205 pflags.BoolVar(&cli.logErr, "error", false, "Set logging to error") 206 pflags.BoolVar(&cli.logTrace, "trace", false, "Set logging to trace") 207 pflags.StringVar(&cli.flagBranch, "branch", "", "Override hub branch on github") 208 209 if err := pflags.MarkHidden("branch"); err != nil { 210 log.Fatalf("failed to hide flag: %s", err) 211 } 212 213 // Look for "-c /path/to/config.yaml" 214 // This duplicates the logic in cobra, but we need to do it before 215 // because feature flags can change which subcommands are available. 216 for i, arg := range os.Args { 217 if arg == "-c" || arg == "--config" { 218 if len(os.Args) > i+1 { 219 ConfigFilePath = os.Args[i+1] 220 } 221 } 222 } 223 224 if err := csconfig.LoadFeatureFlagsFile(ConfigFilePath, log.StandardLogger()); err != nil { 225 log.Fatal(err) 226 } 227 228 if len(os.Args) > 1 { 229 cobra.OnInitialize(cli.initialize) 230 } 231 232 cmd.AddCommand(NewCLIDoc().NewCommand(cmd)) 233 cmd.AddCommand(NewCLIVersion().NewCommand()) 234 cmd.AddCommand(NewCLIConfig(cli.cfg).NewCommand()) 235 cmd.AddCommand(NewCLIHub(cli.cfg).NewCommand()) 236 cmd.AddCommand(NewCLIMetrics(cli.cfg).NewCommand()) 237 cmd.AddCommand(NewCLIDashboard(cli.cfg).NewCommand()) 238 cmd.AddCommand(NewCLIDecisions(cli.cfg).NewCommand()) 239 cmd.AddCommand(NewCLIAlerts(cli.cfg).NewCommand()) 240 cmd.AddCommand(NewCLISimulation(cli.cfg).NewCommand()) 241 cmd.AddCommand(NewCLIBouncers(cli.cfg).NewCommand()) 242 cmd.AddCommand(NewCLIMachines(cli.cfg).NewCommand()) 243 cmd.AddCommand(NewCLICapi(cli.cfg).NewCommand()) 244 cmd.AddCommand(NewCLILapi(cli.cfg).NewCommand()) 245 cmd.AddCommand(NewCompletionCmd()) 246 cmd.AddCommand(NewCLIConsole(cli.cfg).NewCommand()) 247 cmd.AddCommand(NewCLIExplain(cli.cfg).NewCommand()) 248 cmd.AddCommand(NewCLIHubTest(cli.cfg).NewCommand()) 249 cmd.AddCommand(NewCLINotifications(cli.cfg).NewCommand()) 250 cmd.AddCommand(NewCLISupport().NewCommand()) 251 cmd.AddCommand(NewCLIPapi(cli.cfg).NewCommand()) 252 cmd.AddCommand(NewCLICollection().NewCommand()) 253 cmd.AddCommand(NewCLIParser().NewCommand()) 254 cmd.AddCommand(NewCLIScenario().NewCommand()) 255 cmd.AddCommand(NewCLIPostOverflow().NewCommand()) 256 cmd.AddCommand(NewCLIContext().NewCommand()) 257 cmd.AddCommand(NewCLIAppsecConfig().NewCommand()) 258 cmd.AddCommand(NewCLIAppsecRule().NewCommand()) 259 260 if fflag.CscliSetup.IsEnabled() { 261 cmd.AddCommand(NewSetupCmd()) 262 } 263 264 return cmd 265 } 266 267 func main() { 268 cmd := newCliRoot().NewCommand() 269 if err := cmd.Execute(); err != nil { 270 log.Fatal(err) 271 } 272 }