Skip to content

Commit

Permalink
(relay proxy) migrate configuration management from viper to knadh/ko…
Browse files Browse the repository at this point in the history
…anf (#706)
  • Loading branch information
thomaspoignant authored Apr 21, 2023
1 parent 3c29a4f commit 8c5ecbb
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 157 deletions.
153 changes: 102 additions & 51 deletions cmd/relayproxy/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@ package config

import (
"fmt"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag"
"github.com/knadh/koanf/v2"
"net/http"
"os"
"path/filepath"
"strings"
"time"

"github.com/spf13/pflag"
"github.com/spf13/viper"
"go.uber.org/zap"
)

var k = koanf.New(".")
var DefaultRetriever = struct {
Timeout time.Duration
HTTPMethod string
Expand Down Expand Up @@ -38,112 +48,121 @@ var DefaultExporter = struct {
MaxEventInMemory: 100000,
}

// ParseConfig is reading the configuration file
func ParseConfig(log *zap.Logger, version string) (*Config, error) {
viper.Set("version", version)

errBindFlag := viper.BindPFlags(pflag.CommandLine)
if errBindFlag != nil {
// New is reading the configuration file
func New(flagSet *pflag.FlagSet, log *zap.Logger, version string) (*Config, error) {
k.Delete("")

// Default values
_ = k.Load(confmap.Provider(map[string]interface{}{
"listen": "1031",
"host": "localhost",
"fileFormat": "yaml",
"restApiTimeout": 5000,
"pollingInterval": 60000,
}, "."), nil)

// mapping command line parameters to koanf
if errBindFlag := k.Load(posflag.Provider(flagSet, ".", k), nil); errBindFlag != nil {
log.Fatal("impossible to parse flag command line", zap.Error(errBindFlag))
}

// Read config file
configFile := viper.GetString("config")
if configFile != "" {
log.Info("reading config from file", zap.String("fileLocation", configFile))
viper.SetConfigFile(configFile)
configFileLocation, errFileLocation := locateConfigFile(k.String("config"))
if errFileLocation != nil {
log.Info("not using any configuration file", zap.Error(errFileLocation))
} else {
log.Info("reading config from default directories")
viper.SetConfigName("goff-proxy")
viper.AddConfigPath("./")
viper.AddConfigPath("/goff/")
viper.AddConfigPath("/etc/opt/goff/")
ext := filepath.Ext(configFileLocation)
var parser koanf.Parser
switch strings.ToLower(ext) {
case ".toml":
parser = toml.Parser()
break
case ".json":
parser = json.Parser()
break
default:
parser = yaml.Parser()
}

if errBindFile := k.Load(file.Provider(configFileLocation), parser); errBindFile != nil {
log.Error("error loading file", zap.Error(errBindFile))
}
}

viper.SetEnvKeyReplacer(strings.NewReplacer(`.`, `_`))
viper.AutomaticEnv()
setViperDefault()
// Map environment variables
_ = k.Load(env.Provider("", ".", func(s string) string {
return strings.ReplaceAll(strings.ToLower(s), "_", ".")
}), nil)

_ = k.Set("version", version)

err := viper.ReadInConfig()
if err != nil {
return nil, err
}
proxyConf := &Config{}
err = viper.Unmarshal(proxyConf)
if err != nil {
return nil, err
errUnmarshal := k.Unmarshal("", &proxyConf)
if errUnmarshal != nil {
return nil, errUnmarshal
}
return proxyConf, nil
}

// setViperDefault will set default values for the configuration
func setViperDefault() {
viper.SetDefault("listen", "1031")
viper.SetDefault("host", "localhost")
viper.SetDefault("fileFormat", "yaml")
viper.SetDefault("pollingInterval", 60000)
viper.SetDefault("restApiTimeout", 5000)
}

type Config struct {
// ListenPort (optional) is the port we are using to start the proxy
ListenPort int `mapstructure:"listen"`
ListenPort int `mapstructure:"listen" koanf:"listen"`

// HideBanner (optional) if true, we don't display the go-feature-flag relay proxy banner
HideBanner bool `mapstructure:"hideBanner"`
HideBanner bool `mapstructure:"hideBanner" koanf:"hidebanner"`

// EnableSwagger (optional) to have access to the swagger
EnableSwagger bool `mapstructure:"enableSwagger"`
EnableSwagger bool `mapstructure:"enableSwagger" koanf:"enableswagger"`

// Host should be set if you are using swagger (default is localhost)
Host string `mapstructure:"host"`
Host string `mapstructure:"host" koanf:"host"`

// Debug (optional) if true, go-feature-flag relay proxy will run on debug mode, with more logs and custom responses
Debug bool `mapstructure:"debug"`
Debug bool `mapstructure:"debug" koanf:"debug"`

// PollingInterval (optional) Poll every X time
// The minimum possible is 1 second
// Default: 60 seconds
PollingInterval int `mapstructure:"pollingInterval"`
PollingInterval int `koanf:"pollinginterval" mapstructure:"pollingInterval"`

// FileFormat (optional) is the format of the file to retrieve (available YAML, TOML and JSON)
// Default: YAML
FileFormat string `mapstructure:"fileFormat"`
FileFormat string `mapstructure:"fileFormat" koanf:"fileformat"`

// StartWithRetrieverError (optional) If true, the relay proxy will start even if we did not get any flags from
// the retriever. It will serve only default values until the retriever returns the flags.
// The init method will not return any error if the flag file is unreachable.
// Default: false
StartWithRetrieverError bool `mapstructure:"startWithRetrieverError"`
StartWithRetrieverError bool `mapstructure:"startWithRetrieverError" koanf:"startwithretrievererror"`

// Retriever is the configuration on how to retrieve the file
Retriever *RetrieverConf `mapstructure:"retriever"`
Retriever *RetrieverConf `mapstructure:"retriever" koanf:"retriever"`

// Retrievers is the exact same things than Retriever but allows to give more than 1 retriever at the time.
// We are dealing with config files in order, if you have the same flag name in multiple files it will be override
// based of the order of the retrievers in the slice.
//
// Note: If both Retriever and Retrievers are set, we will start by calling the Retriever and,
// after we will use the order of Retrievers.
Retrievers *[]RetrieverConf `mapstructure:"retrievers"`
Retrievers *[]RetrieverConf `mapstructure:"retrievers" koanf:"retrievers"`

// Exporter is the configuration on how to export data
Exporter *ExporterConf `mapstructure:"exporter"`
Exporter *ExporterConf `mapstructure:"exporter" koanf:"exporter"`

// Notifiers is the configuration on where to notify a flag change
Notifiers []NotifierConf `mapstructure:"notifier"`
Notifiers []NotifierConf `mapstructure:"notifier" koanf:"notifier"`

// RestAPITimeout is the timeout on the API.
RestAPITimeout int `mapstructure:"restApiTimeout"`
RestAPITimeout int `mapstructure:"restApiTimeout" koanf:"restapitimeout"`

// Version is the version of the relay-proxy
Version string
Version string `mapstructure:"version" koanf:"version"`

// APIKeys list of API keys that authorized to use endpoints
APIKeys []string
APIKeys []string `mapstructure:"apiKeys" koanf:"apikeys"`

// StartAsAwsLambda (optional) if true the relay proxy will start ready to be launch as AWS Lambda
StartAsAwsLambda bool `mapstructure:"startAsAwsLambda"`
StartAsAwsLambda bool `mapstructure:"startAsAwsLambda" koanf:"startasawslambda"`

// ---- private fields

Expand Down Expand Up @@ -207,3 +226,35 @@ func (c *Config) IsValid() error {

return nil
}

// locateConfigFile is selecting the configuration file we will use.
func locateConfigFile(inputFilePath string) (string, error) {
filename := "goff-proxy"
defaultLocations := []string{
"./",
"/goff/",
"/etc/opt/goff/",
}
supportedExtensions := []string{
"yaml",
"toml",
"json",
}

if inputFilePath != "" {
if _, err := os.Stat(inputFilePath); err != nil {
return "", fmt.Errorf("impossible to find config file %s", inputFilePath)
}
return inputFilePath, nil
}
for _, location := range defaultLocations {
for _, ext := range supportedExtensions {
configFile := fmt.Sprintf("%s%s.%s", location, filename, ext)
if _, err := os.Stat(configFile); err == nil {
return configFile, nil
}
}
}
return "", fmt.Errorf(
"impossible to find config file in the default locations [%s]", strings.Join(defaultLocations, ","))
}
Loading

0 comments on commit 8c5ecbb

Please sign in to comment.