config

package
v1.70.0 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2024 License: Apache-2.0 Imports: 9 Imported by: 0

README

CLI / Service configuration

Viper provides quite a lot of utilities to retrieve configuration values and integrates well with cobra which is a reference for CLI implementation in go.

Nonetheless viper has some gaps we tried to fill for ease of configuration:

  • configuration value validation at configuration load
  • .env configuration
  • easy mapping/deserialisation between environment variables and complex nested configuration structures.

The idea is to have the ability to have a complex configuration structure for the project so that values can easily be shared throughout it without requiring viper everywhere. We also tried to gather value in a tree structure in order to categorise them into components configuration instead of having all configuration at the same level in a key/value store.

Usage

Configuration structure

The first step is to define a complex configuration structure for your project and add a Validate() method at each level which will be called during load in order to ensure values are as expected. For validation, we use github.com/go-ozzo/ozzo-validation/v4 but other libraries/method could be used. It is also advised to provide a method which returns the structure with defaults.

Please look at the following as an example:

type DummyConfiguration struct {
	Host              string        `mapstructure:"host"`
	Port              int           `mapstructure:"port"`
	DB                string        `mapstructure:"db"`
	User              string        `mapstructure:"user"`
	Password          string        `mapstructure:"password"`
	HealthCheckPeriod time.Duration `mapstructure:"healthcheck_period"`
}

func (cfg *DummyConfiguration) Validate() error {
	// Validate Embedded Structs
	err := ValidateEmbedded(cfg)
	if err != nil {
		return err
	}

	return validation.ValidateStruct(cfg,
		validation.Field(&cfg.Host, validation.Required),
		validation.Field(&cfg.Port, validation.Required),
		validation.Field(&cfg.DB, validation.Required),
		validation.Field(&cfg.User, validation.Required),
		validation.Field(&cfg.Password, validation.Required),
	)
}

func DefaultDummyConfiguration() DummyConfiguration {
	return DummyConfiguration{
		Port:              5432,
		HealthCheckPeriod: time.Second,
	}
}

Note the mapstructure tag. This will be used as part of the environment value name. The structure can be complex and of different levels. The name of the environment value mapped to a configuration field will be the combination of the mapstructure tags separated by underscore _. for example, CLI_HEALTHCHECK_PERIOD environment variable will refer to the HealthCheckPeriod field in the configuration structure above if the prefix used when loading configuration (see below) is CLI.

Configuration load

Load or LoadFromViper will load values from the environment (including from .env files) and assign them to the different configuration fields in the structure passed. Type conversion will be done automatically and if not set and a default value is provided, then the default value will be retained. Configuration values are then validated using the different Validate() methods provided. In order to easily identify environment variables, a prefix can be provided when loading the configuration. This will tell the system to only consider environment variables with this prefix.

Integration with Cobra CLI arguments

cobra and viper are well integrated in the way that you can bind CLI arguments with configuration values. We provide some utilities (BindFlagToEnv) to leverage this binding so that it works with the configuration system described above and limits the number of hardcoded names or global values usually seen in CLI code generated from cobra.

    // Create a viper session instead of using global configuration
	session := viper.New()

	config := &ConfigurationStructConTainingFlag1{}
	defaults := DefaultConfiguration()

	flagSet := pflag.FlagSet{}
	prefix := "ENV_PREFIX"
    // Define CLI flags
	flagSet.String("f", "flag", "a cli flag")
    // Bind flags to environment variables
	err = BindFlagToEnv(session, prefix, "ENV_PREFIX_FLAG1", flagSet.Lookup("flag"))
    ...
    // Load configuration from the environment
    err = LoadFromViper(session, prefix, config, defaults)

Documentation

Overview

Package config provides utilities to load configuration from an environment and perform validation at load time.

  • Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
  • SPDX-License-Identifier: Apache-2.0

Index

Constants

View Source
const (
	EnvVarSeparator = "_"
	DotEnvFile      = ".env"
)

Variables

This section is empty.

Functions

func BindFlagToEnv

func BindFlagToEnv(viperSession *viper.Viper, envVarPrefix string, envVar string, flag *pflag.Flag) (err error)

BindFlagToEnv binds pflags to environment variable. Envvar is the environment variable string with or without the prefix envVarPrefix

func DetermineConfigurationEnvironmentVariables added in v1.16.0

func DetermineConfigurationEnvironmentVariables(appName string, configurationToDecode IServiceConfiguration) (defaults map[string]interface{}, err error)

DetermineConfigurationEnvironmentVariables returns all the environment variables corresponding to a configuration structure as well as all the default values currently set.

func Load

func Load(envVarPrefix string, configurationToSet IServiceConfiguration, defaultConfiguration IServiceConfiguration) error

Load loads the configuration from the environment (i.e. .env file, environment variables) and puts the entries into the configuration object configurationToSet. If not found in the environment, the values will come from the default values defined in defaultConfiguration. `envVarPrefix` defines a prefix that ENVIRONMENT variables will use. E.g. if your prefix is "spf", the env registry will look for env variables that start with "SPF_". make sure that the tags on the fields of configurationToSet are properly set using only `[_1-9a-zA-Z]` characters.

func LoadFromConfigurationFile added in v1.31.0

func LoadFromConfigurationFile(viperSession *viper.Viper, configFile string) (err error)

LoadFromConfigurationFile loads the configuration from the environment. If the format is not supported, an error is raised and the same happens if the file cannot be found. Supported formats are the same as what viper(https://github.com/spf13/viper#what-is-viper) supports

func LoadFromEnvironment added in v1.31.0

func LoadFromEnvironment(viperSession *viper.Viper, envVarPrefix string, configurationToSet IServiceConfiguration, defaultConfiguration IServiceConfiguration, configFile string) (err error)

LoadFromEnvironment is the same as `LoadFromViper` but also gives the ability to load the configuration from a configuration file as long as the format is supported by [Viper](https://github.com/spf13/viper#what-is-viper) Important note: Viper's precedence order is maintained: 1) values set using explicit calls to `Set` 2) flags 3) environment (variables or `.env`) 4) configuration file 5) key/value store 6) default values (set via flag default values, or calls to `SetDefault` or via `defaultConfiguration` argument provided) Nonetheless, when it comes to default values. It differs slightly from Viper as default values from the default Configuration (i.e. `defaultConfiguration` argument provided) will take precedence over defaults set via `SetDefault` or flags unless they are considered empty values according to `reflection.IsEmpty`.

func LoadFromViper

func LoadFromViper(viperSession *viper.Viper, envVarPrefix string, configurationToSet IServiceConfiguration, defaultConfiguration IServiceConfiguration) error

LoadFromViper is the same as `Load` but instead of creating a new viper session, reuse the one provided. Important note: Viper's precedence order is maintained: 1) values set using explicit calls to `Set` 2) flags 3) environment (variables or `.env`) 4) key/value store 5) default values (set via flag default values, or calls to `SetDefault` or via `defaultConfiguration` argument provided) Nonetheless, when it comes to default values. It differs slightly from Viper as default values from the default Configuration (i.e. `defaultConfiguration` argument provided) will take precedence over defaults set via `SetDefault` or flags unless they are considered empty values according to `reflection.IsEmpty`.

func ValidateEmbedded

func ValidateEmbedded(cfg Validator) error

ValidateEmbedded uses reflection to find embedded structs and validate them

Types

type IServiceConfiguration

type IServiceConfiguration interface {
	Validator
}

IServiceConfiguration defines a typical service configuration.

type Validator

type Validator interface {
	Validate() error
}

Validator defines an object which can perform some validation on itself.

Jump to

Keyboard shortcuts

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