loader/loader.go (77 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 loader import ( "fmt" "path/filepath" "github.com/elastic/elastic-agent-libs/logp" "github.com/elastic/go-ucfg" "github.com/elastic/go-ucfg/cfgutil" ) // Loader is used to load configuration from the paths // including appending multiple input configurations. type Loader struct { logger *logp.Logger inputsFolder string } // NewLoader creates a new Loader instance to load configuration // files from different paths. func NewLoader(logger *logp.Logger, inputsFolder string) *Loader { return &Loader{logger: logger, inputsFolder: inputsFolder} } // Load iterates over the list of files and loads the confguration from them. // If a configuration file is under the folder set in `agent.config.inputs.path` // it is appended to a list. If it is a regular config file, it is merged into // the result config. The list of input configurations is merged into the result // last. func (l *Loader) Load(files []string) (*Config, error) { inputsList := make([]*ucfg.Config, 0) merger := cfgutil.NewCollector(nil) for _, f := range files { cfg, err := LoadFile(f) if err != nil { if l.isFileUnderInputsFolder(f) { return nil, fmt.Errorf("failed to load external configuration file '%s': %w. Are you sure it contains an inputs section?", f, err) } return nil, fmt.Errorf("failed to load configuration file '%s': %w", f, err) } l.logger.Debugf("Loaded configuration from %s", f) if l.isFileUnderInputsFolder(f) { inp, err := getInput(cfg) if err != nil { return nil, fmt.Errorf("cannot get configuration from '%s': %w", f, err) } inputsList = append(inputsList, inp...) l.logger.Debugf("Loaded %s input(s) from configuration from %s", len(inp), f) } else { if err := merger.Add(cfg.access(), err); err != nil { return nil, fmt.Errorf("failed to merge configuration file '%s' to existing one: %w", f, err) } l.logger.Debugf("Merged configuration from %s into result", f) } } config := merger.Config() // if there is no input configuration, return what we have collected. if len(inputsList) == 0 { l.logger.Debugf("Merged all configuration files from %v, no external input files", files) return newConfigFrom(config), nil } // merge inputs sections from the last standalone configuration // file and all files from the inputs folder start := 0 if config.HasField("inputs") { var err error start, err = config.CountField("inputs") if err != nil { return nil, fmt.Errorf("failed to count the number of inputs in the configuration: %w", err) } } for i, ll := range inputsList { if err := config.SetChild("inputs", start+i, ll); err != nil { return nil, fmt.Errorf("failed to add inputs to result configuration: %w", err) } } l.logger.Debugf("Merged all configuration files from %v, with external input files", files) return newConfigFrom(config), nil } func getInput(c *Config) ([]*ucfg.Config, error) { tmpConfig := struct { Inputs []*ucfg.Config `config:"inputs"` }{make([]*ucfg.Config, 0)} if err := c.Unpack(&tmpConfig); err != nil { return nil, fmt.Errorf("failed to parse inputs section from configuration: %w", err) } return tmpConfig.Inputs, nil } func (l *Loader) isFileUnderInputsFolder(f string) bool { if matches, err := filepath.Match(l.inputsFolder, f); !matches || err != nil { return false } return true }