config

package
v0.0.0-...-4e66fdd Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 30, 2024 License: Apache-2.0 Imports: 10 Imported by: 11

README

Veraison Services Configuration

Veraison services use Viper for configuration. Viper is great, but it can be a little too accepting. Config loader implemented here adds a validation layer on top of viper that allows pre-processing config collected by Viper before it is used. Full recognition before processing.

The General Idea

  • Configuration for each component is defined by its own struct.
  • The loader is given an instance of the struct to populate. Hard-coded defaults can be provided by setting field values of this instance.
  • Viper is used to collect configuration for all components across various sources (files, environment variables, set by client code, etc.).
  • Viper's Sub() is used to create a Viper instance with the sub-tree of collected configuration relevant to the component.
  • The component-relevant viper is passed to the loader that uses it to populate the config struct. Loader does this by calling Viper's AllSettings() which returns a map[string]interface{}; the loader can also be passed such a map directly.
  • If the struct defines a Validate() error method, the loader will then invoke that. This can be used to validate additional constraints on the settings that go beyond their basic type.
  • The component then uses configuration from the struct without needing to worry about validating each individual setting.

Validation

The loader enforces the following invariants:

  • Setting values are of the correct type (this is ensured by unmarshalling into a struct).
  • All settings have been set. There is no explicit concept of optional settings; settings become optional if a default is provided. This ensures that a component always has valid values to operate on.
  • Everything collected by Viper has been been processed, there are no "extras". This helps catch typos and dead config that is not actually used to influence components. This is why Sub() should be used to extract the component-relevant sub-tree from Viper before giving to the loader. (note: this constraint can be relaxed by creating a non-exclusive loader with NewNonExclusiveLoader(). This may be useful for processing nested configs.)
  • Additional constraints for a field can be specified using a govalidator tag. Valid values for the tag is listed in the `govalidator README.
  • Any additional, including inter-setting (i.e. co-constraints), constraints can be specified by implementing Validate() error for the config struct.

Field names in configuration

The loader uses mapstructure to unmarshal settings from a source map into the destination struct. Setting names from configuration source are matched to struct field names via strings.EqualFold. If setting names are different from field names (e.g. if they contain hyphens or other special characters), it is possible to specify the name using "mapstructure" tag. E.g.

type Config struct {
        HyphenatedSetting string `mapstructure:"hyphenated-setting"`
}

Handling of Defaults

There are two ways to provide default values from the code, depending on whether the default is being set by the component itself (e.g. vts server setting default address it will listen on), or by another component using it (e.g. polcli specifying defaults for the policy store).

If the defaults are set by the component itself, they maybe set simply by specifying them as field values when instantiating the config struct.

If the defaults come from the instantiating code, rather than component itself, they maybe set inside the Viper instance being passed to the component using Viper's SetDefault().

Zero-values in Defaults

It is not possible to distinguish between an unset struct field, and one that has been set to that type's Zero value. (Analogously, when Get'ing an unset setting in Viper, one gets the getter type's Zero value.) Since the goal is robustness, we treat Zero value defaults as unset and raise an error if a value for that field hasn't been provided in the input (note: zero values in the input are OK, as their mere presence indicates that they have been explicitly set).

If you wish to treat a field as defaulting to its type's zero value, rather than as unset, you have to set config:"zerodefault" tag on that field:

type Config struct {
        MustBeSet string
        CanBeEmpty string `config:"zerodefault"`
        CanBeZero int `config:"zerodefault"`
}

Example

import (
        "fmt"
        "log"
        "net/url"
)

type Config struct {
	Host       string
	Port       uint16
}

// Optional Validate()
func (o Config) Validate() error {
        if _, err := url.ParseRequestURI(c.Host); err != nil {
                return fmt.Errorf("bad host: %w", err)
        }

        return nil
}

func main() {
	var myConfig Config

	loader := NewLoader(&myConfig)

        // It is also possible to load directly from a Viper instance using
        // LoadFromViper().
	err := loader.LoadFromMap(map[string]interface{}{
		"host": "example.com",
		"port": 8080,
	})
	if err != nil {
            log.Fatal(err)
        }

        // myConfig.Host and myConfig.Port have now been set to valid values

}

Documentation

Overview

Copyright 2022-2023 Contributors to the Veraison project. SPDX-License-Identifier: Apache-2.0

Copyright 2023 Contributors to the Veraison project. SPDX-License-Identifier: Apache-2.0

Copyright 2022-2023 Contributors to the Veraison project. SPDX-License-Identifier: Apache-2.0

Copyright 2022-2023 Contributors to the Veraison project. SPDX-License-Identifier: Apache-2.0

Index

Constants

This section is empty.

Variables

View Source
var (
	DisplayVersion *bool   = flag.BoolP("version", "v", false, "print version and exit")
	DisplayHelp    *bool   = flag.BoolP("help", "h", false, "print help and exit")
	File           *string = flag.StringP("config", "c", "config.yaml", "configuration file")
)

Veraison services common command line flags: -c, --config <configuration-file> (default "config.yaml") -h, --help -v, --version

View Source
var Developer = "Veraison Project"
View Source
var ErrNilConfig = errors.New("nil configuration")
View Source
var SchemeLoader = "plugins"

Valid values: "plugins", "builtin"

View Source
var Version = "N/A"

Functions

func CmdLine

func CmdLine()

CmdLine parses the command line an

func GetSubs

func GetSubs(v *viper.Viper, names ...string) (map[string]*viper.Viper, error)

GetSubs returns a map of name onto the corresponding Sub from the provided Viper, ensuring that it is never nil. If the provided Viper does not contain the specified Sub (v.Sub(name) returns nil), an error will be returned, unless the name is specified as optional by prefixing it with "*", in which case a new empty Viper is returned instead.

func ReadRawConfig

func ReadRawConfig(path string, allowNotFound bool) (*viper.Viper, error)

ReadRawConfig instantiates a Viper and uses it to read in configuration. If path is specified as something other than an empty string, Viper will attempt to read it, inferring the format from the file extension. Otherwise, it will for a file called "config.yaml" inside current working directory. An error will be returned if there are problems reading configuration. Unless allowNotFount is set to true, this includes the config file (either the explicitly specified one or the implicit config.yaml) not being present.

Types

type IValidatable

type IValidatable interface {
	Validate() error
}

IValidatable defines an interface of objects that can self-validate.

type Loader

type Loader struct {
	// contains filtered or unexported fields
}

Loader is responsible for loading config from a source into a struct instance.

func NewLoader

func NewLoader(dest interface{}) *Loader

NewLoader creates a new loader. dest must be a pointer to a struct instance to be populated, other wise, nil returned.

func NewNonExclusiveLoader

func NewNonExclusiveLoader(dest interface{}) *Loader

NewNonExclusiveLoader is just like NewLoader, but the loader returned allows ther to be unknown settings in the source.

func (*Loader) Init

func (o *Loader) Init(dest interface{}) error

Init initializes the loader with the specified destination. If dest is not a pointer to a struct instance, an error is returned.

func (Loader) LoadFromMap

func (o Loader) LoadFromMap(source map[string]interface{}) error

LoadFromMap populates the destination struct instance with values from the specified map[string]interface{} source.

func (Loader) LoadFromViper

func (o Loader) LoadFromViper(source *viper.Viper) error

LoadFromViper populates the destination struct instance with values from the specified *viper.Viper source.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL