libbeat/cfgfile/cfgfile.go (182 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package cfgfile import ( "flag" "fmt" "os" "path/filepath" "sync" "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/fleetmode" "github.com/elastic/elastic-agent-libs/config" "github.com/elastic/elastic-agent-libs/logp" ) // Evil package level globals var ( once sync.Once configfiles *config.StringsFlag overwrites *config.C defaults *config.C homePath *string configPath *string ) func Initialize() { once.Do(func() { // The default config cannot include the beat name as // it is not initialized when this variable is // created. See ChangeDefaultCfgfileFlag which should // be called prior to flags.Parse(). configfiles = config.StringArrFlag(nil, "c", "beat.yml", "Configuration file, relative to path.config") overwrites = config.SettingFlag(nil, "E", "Configuration overwrite") defaults = config.MustNewConfigFrom(map[string]interface{}{ "path": map[string]interface{}{ "home": ".", // to be initialized by beat "config": "${path.home}", "data": filepath.Join("${path.home}", "data"), "logs": filepath.Join("${path.home}", "logs"), }, }) homePath = config.ConfigOverwriteFlag(nil, overwrites, "path.home", "path.home", "", "Home path") configPath = config.ConfigOverwriteFlag(nil, overwrites, "path.config", "path.config", "", "Configuration path") _ = config.ConfigOverwriteFlag(nil, overwrites, "path.data", "path.data", "", "Data path") _ = config.ConfigOverwriteFlag(nil, overwrites, "path.logs", "path.logs", "", "Logs path") }) } // OverrideChecker checks if a config should be overwritten. type OverrideChecker func(*config.C) bool // ConditionalOverride stores a config which needs to overwrite the existing config based on the // result of the Check. type ConditionalOverride struct { Check OverrideChecker Config *config.C } // ChangeDefaultCfgfileFlag replaces the value and default value for // the `-c` flag so that it reflects the beat name. It will call // Initialize() to register the `-c` flags func ChangeDefaultCfgfileFlag(beatName string) error { Initialize() configfiles.SetDefault(beatName + ".yml") return nil } // GetDefaultCfgfile gets the full path of the default config file. Understood // as the first value for the `-c` flag. By default this will be `<beatname>.yml` func GetDefaultCfgfile() string { if len(configfiles.List()) == 0 { return "" } cfg := configfiles.List()[0] cfgpath := GetPathConfig() if !filepath.IsAbs(cfg) { return filepath.Join(cfgpath, cfg) } return cfg } // HandleFlags adapts default config settings based on command line // flags. This also stores if -E management.enabled=true was set on // command line to determine if running the Beat under agent. It will // call Initialize() to register the flags like `-E`. func HandleFlags() error { Initialize() // default for the home path is the binary location home, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { if *homePath == "" { return fmt.Errorf("The absolute path to %s could not be obtained. %w", os.Args[0], err) } home = *homePath } _ = defaults.SetString("path.home", -1, home) if len(overwrites.GetFields()) > 0 { common.PrintConfigDebugf(overwrites, "CLI setting overwrites (-E flag):") } // Enable check to see if beat is running under Agent // This is stored in a package so the modules which don't have // access to the config can check this value. type management struct { Enabled bool `config:"management.enabled"` } var managementSettings management cfgFlag := flag.Lookup("E") if cfgFlag == nil { fleetmode.SetAgentMode(false) return nil } cfgObject, _ := cfgFlag.Value.(*config.SettingsFlag) cliCfg := cfgObject.Config() err = cliCfg.Unpack(&managementSettings) if err != nil { fleetmode.SetAgentMode(false) return nil //nolint:nilerr // unpacking failing isn't an error for this case } fleetmode.SetAgentMode(managementSettings.Enabled) return nil } // Deprecated: Please use Load(). // // Read reads the configuration from a YAML file into the given interface // structure. If path is empty this method reads from the configuration // file specified by the '-c' command line flag. func Read(out interface{}, path string) error { config, err := Load(path, nil) if err != nil { return err } return config.Unpack(out) } // Load reads the configuration from a YAML file structure. If path is empty // this method reads from the configuration file specified by the '-c' command // line flag. // This function cares about the underlying fleet setting, and if beats is running with // the management.enabled flag, Load() will bypass reading a config file, and merely merge any overrides. func Load(path string, beatOverrides []ConditionalOverride) (*config.C, error) { var c *config.C var err error cfgpath := GetPathConfig() if !fleetmode.Enabled() { if path == "" { list := []string{} for _, cfg := range configfiles.List() { if !filepath.IsAbs(cfg) { list = append(list, filepath.Join(cfgpath, cfg)) } else { list = append(list, cfg) } } c, err = common.LoadFiles(list...) } else { if !filepath.IsAbs(path) { path = filepath.Join(cfgpath, path) } c, err = common.LoadFile(path) } if err != nil { return nil, err } } else { c = config.NewConfig() } if beatOverrides != nil { merged := defaults for _, o := range beatOverrides { if o.Check(c) { merged, err = config.MergeConfigs(merged, o.Config) if err != nil { return nil, err } } } c, err = config.MergeConfigs( merged, c, overwrites, ) if err != nil { return nil, err } } else { c, err = config.MergeConfigs( defaults, c, overwrites, ) if err != nil { return nil, err } } common.PrintConfigDebugf(c, "Complete configuration loaded:") return c, nil } // LoadList loads a list of configs data from the given file. func LoadList(file string) ([]*config.C, error) { logp.Debug("cfgfile", "Load config from file: %s", file) rawConfig, err := common.LoadFile(file) if err != nil { return nil, fmt.Errorf("invalid config: %w", err) } var c []*config.C err = rawConfig.Unpack(&c) if err != nil { return nil, fmt.Errorf("error reading configuration from file %s: %w", file, err) } return c, nil } func SetConfigPath(path string) { *configPath = path } // GetPathConfig returns ${path.config}. If ${path.config} is not set, // ${path.home} is returned. It will call Initialize to ensure that // `path.config` and `path.home` are set. func GetPathConfig() string { Initialize() if *configPath != "" { return *configPath } else if *homePath != "" { return *homePath } // TODO: Do we need this or should we always return *homePath? return "" }