Browse Source

Allow feature.yml to change available subcommands (#2156)

mmetc 2 years ago
parent
commit
38ab6be7c2

+ 16 - 2
cmd/crowdsec-cli/main.go

@@ -209,6 +209,21 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall
 		log.Fatalf("failed to hide flag: %s", err)
 	}
 
+	// Look for "-c /path/to/config.yaml"
+	// This duplicates the logic in cobra, but we need to do it before
+	// because feature flags can change which subcommands are available.
+	for i, arg := range os.Args {
+		if arg == "-c" || arg == "--config" {
+			if len(os.Args) > i+1 {
+				ConfigFilePath = os.Args[i+1]
+			}
+		}
+	}
+
+	if err := csconfig.LoadFeatureFlagsFile(ConfigFilePath, log.StandardLogger()); err != nil {
+		log.Fatal(err)
+	}
+
 	if len(os.Args) > 1 {
 		cobra.OnInitialize(initConfig)
 	}
@@ -249,7 +264,6 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall
 	}
 
 	if err := rootCmd.Execute(); err != nil {
-		log.NewEntry(log.StandardLogger()).Log(log.FatalLevel, err)
-		os.Exit(1)
+		log.Fatal(err)
 	}
 }

+ 19 - 14
cmd/crowdsec/main.go

@@ -191,9 +191,14 @@ func newLogLevel(curLevelPtr *log.Level, f *Flags) *log.Level {
 }
 
 // LoadConfig returns a configuration parsed from configuration file
-func LoadConfig(cConfig *csconfig.Config) error {
+func LoadConfig(configFile string, disableAgent bool, disableAPI bool, quiet bool) (*csconfig.Config, error) {
+	cConfig, err := csconfig.NewConfig(configFile, disableAgent, disableAPI, quiet)
+	if err != nil {
+		return nil, err
+	}
+
 	if (cConfig.Common == nil || *cConfig.Common == csconfig.CommonCfg{}) {
-		return fmt.Errorf("unable to load configuration: common section is empty")
+		return nil, fmt.Errorf("unable to load configuration: common section is empty")
 	}
 
 	cConfig.Common.LogLevel = newLogLevel(cConfig.Common.LogLevel, flags)
@@ -207,7 +212,7 @@ func LoadConfig(cConfig *csconfig.Config) error {
 
 	// Configuration paths are dependency to load crowdsec configuration
 	if err := cConfig.LoadConfigurationPaths(); err != nil {
-		return err
+		return nil, err
 	}
 
 	if flags.SingleFileType != "" && flags.OneShotDSN != "" {
@@ -221,31 +226,31 @@ func LoadConfig(cConfig *csconfig.Config) error {
 		cConfig.Common.LogMaxSize, cConfig.Common.LogMaxFiles,
 		cConfig.Common.LogMaxAge, cConfig.Common.CompressLogs,
 		cConfig.Common.ForceColorLogs); err != nil {
-		return err
+		return nil, err
 	}
 
-	if err := csconfig.LoadFeatureFlagsFile(cConfig, log.StandardLogger()); err != nil {
-		return err
+	if err := csconfig.LoadFeatureFlagsFile(configFile, log.StandardLogger()); err != nil {
+		return nil, err
 	}
 
 	if !flags.DisableAgent {
 		if err := cConfig.LoadCrowdsec(); err != nil {
-			return err
+			return nil, err
 		}
 	}
 
 	if !flags.DisableAPI {
 		if err := cConfig.LoadAPIServer(); err != nil {
-			return err
+			return nil, err
 		}
 	}
 
 	if !cConfig.DisableAgent && (cConfig.API == nil || cConfig.API.Client == nil || cConfig.API.Client.Credentials == nil) {
-		return errors.New("missing local API credentials for crowdsec agent, abort")
+		return nil, errors.New("missing local API credentials for crowdsec agent, abort")
 	}
 
 	if cConfig.DisableAPI && cConfig.DisableAgent {
-		return errors.New("You must run at least the API Server or crowdsec")
+		return nil, errors.New("You must run at least the API Server or crowdsec")
 	}
 
 	if flags.TestMode && !cConfig.DisableAgent {
@@ -253,15 +258,15 @@ func LoadConfig(cConfig *csconfig.Config) error {
 	}
 
 	if flags.OneShotDSN != "" && flags.SingleFileType == "" {
-		return errors.New("-dsn requires a -type argument")
+		return nil, errors.New("-dsn requires a -type argument")
 	}
 
 	if flags.Transform != "" && flags.OneShotDSN == "" {
-		return errors.New("-transform requires a -dsn argument")
+		return nil, errors.New("-transform requires a -dsn argument")
 	}
 
 	if flags.SingleFileType != "" && flags.OneShotDSN == "" {
-		return errors.New("-type requires a -dsn argument")
+		return nil, errors.New("-type requires a -dsn argument")
 	}
 
 	if flags.SingleFileType != "" && flags.OneShotDSN != "" {
@@ -290,7 +295,7 @@ func LoadConfig(cConfig *csconfig.Config) error {
 		log.Infof("Enabled feature flags: %s", fflist)
 	}
 
-	return nil
+	return cConfig, nil
 }
 
 // crowdsecT0 can be used to measure start time of services,

+ 1 - 5
cmd/crowdsec/run_in_svc.go

@@ -34,11 +34,7 @@ func StartRunSvc() error {
 		},
 	})
 
-	cConfig, err = csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false)
-	if err != nil {
-		return err
-	}
-	if err := LoadConfig(cConfig); err != nil {
+	if cConfig, err = LoadConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false); err != nil {
 		return err
 	}
 

+ 1 - 4
cmd/crowdsec/run_in_svc_windows.go

@@ -61,13 +61,10 @@ func WindowsRun() error {
 		err     error
 	)
 
-	cConfig, err = csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false)
+	cConfig, err = LoadConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false)
 	if err != nil {
 		return err
 	}
-	if err := LoadConfig(cConfig); err != nil {
-		return err
-	}
 	// Configure logging
 	log.Infof("Crowdsec %s", cwversion.VersionStr())
 

+ 1 - 5
cmd/crowdsec/serve.go

@@ -54,15 +54,11 @@ func reloadHandler(sig os.Signal) (*csconfig.Config, error) {
 	crowdsecTomb = tomb.Tomb{}
 	pluginTomb = tomb.Tomb{}
 
-	cConfig, err := csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false)
+	cConfig, err := LoadConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false)
 	if err != nil {
 		return nil, err
 	}
 
-	if err = LoadConfig(cConfig); err != nil {
-		return nil, err
-	}
-
 	if !cConfig.DisableAPI {
 		if flags.DisableCAPI {
 			log.Warningf("Communication with CrowdSec Central API disabled from args")

+ 1 - 5
cmd/crowdsec/win_service.go

@@ -97,15 +97,11 @@ func runService(name string) error {
 		log.Warnf("Failed to open event log: %s", err)
 	}
 
-	cConfig, err := csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false)
+	cConfig, err := LoadConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false)
 	if err != nil {
 		return err
 	}
 
-	if err := LoadConfig(cConfig); err != nil {
-		return err
-	}
-
 	log.Infof("starting %s service", name)
 	winsvc := crowdsec_winservice{config: cConfig}
 

+ 0 - 7
pkg/csconfig/cscli.go

@@ -1,9 +1,5 @@
 package csconfig
 
-import (
-	log "github.com/sirupsen/logrus"
-)
-
 /*cscli specific config, such as hub directory*/
 type CscliCfg struct {
 	Output             string            `yaml:"output,omitempty"`
@@ -26,9 +22,6 @@ func (c *Config) LoadCSCLI() error {
 	if err := c.LoadConfigurationPaths(); err != nil {
 		return err
 	}
-	if err := LoadFeatureFlagsFile(c, log.StandardLogger()); err != nil {
-		return err
-	}
 	c.Cscli.ConfigDir = c.ConfigPaths.ConfigDir
 	c.Cscli.DataDir = c.ConfigPaths.DataDir
 	c.Cscli.HubDir = c.ConfigPaths.HubDir

+ 6 - 3
pkg/csconfig/fflag.go

@@ -20,9 +20,12 @@ func LoadFeatureFlagsEnv(logger *log.Logger) error {
 }
 
 
-// LoadFeatureFlags parses {ConfigDir}/feature.yaml to enable feature flags.
-func LoadFeatureFlagsFile(cConfig *Config, logger *log.Logger) error {
-	featurePath := filepath.Join(cConfig.ConfigPaths.ConfigDir, "feature.yaml")
+// LoadFeatureFlags parses feature.yaml to enable feature flags.
+// The file is in the same directory as config.yaml, which is provided
+// as the fist parameter. This can be different than ConfigPaths.ConfigDir
+func LoadFeatureFlagsFile(configPath string, logger *log.Logger) error {
+	dir := filepath.Dir(configPath)
+	featurePath := filepath.Join(dir, "feature.yaml")
 
 	if err := fflag.Crowdsec.SetFromYamlFile(featurePath, logger); err != nil {
 		return fmt.Errorf("file %s: %s", featurePath, err)

+ 12 - 1
test/bats/01_cscli.bats

@@ -282,7 +282,7 @@ teardown() {
     assert_output - <"$BATS_TEST_DIRNAME"/testdata/explain/explain-log.txt
 }
 
-@test "Allow variable expansion and literal \$ characters in passwords' {
+@test 'Allow variable expansion and literal $ characters in passwords' {
     export DB_PASSWORD='P@ssw0rd'
     # shellcheck disable=SC2016
     config_set '.db_config.password="$DB_PASSWORD"'
@@ -321,3 +321,14 @@ teardown() {
     rune -0 cscli doc
     assert_file_exist "doc/cscli_setup.md"
 }
+
+@test "feature.yaml for subcommands" {
+    # it is possible to enable subcommands with feature flags defined in feature.yaml
+
+    rune -1 cscli setup
+    assert_stderr --partial 'unknown command \"setup\" for \"cscli\"'
+    CONFIG_DIR=$(dirname "$CONFIG_YAML")
+    echo ' - cscli_setup' >> "$CONFIG_DIR"/feature.yaml
+    rune -0 cscli setup
+    assert_output --partial 'cscli setup [command]'
+}