mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-04 01:09:36 +02:00
internal/config: Switch to using struct scoped viper instance (#332)
* Switch to using struct scoped viper instance * Rename NewXXXOptions * Handle unchecked errors from viper.BindEnv
This commit is contained in:
parent
5df0ff500c
commit
251ab0d527
6 changed files with 98 additions and 52 deletions
|
@ -25,7 +25,8 @@ import (
|
|||
// DisableHeaderKey is the key used to check whether to disable setting header
|
||||
const DisableHeaderKey = "disable"
|
||||
|
||||
// Options are the global environmental flags used to set up pomerium's services.
|
||||
// Options are the global environmental flags used to set up pomerium's services. Use NewXXXOptions() methods
|
||||
// for a safely initialized data structure.
|
||||
type Options struct {
|
||||
// Debug outputs human-readable logs to Stdout.
|
||||
Debug bool `mapstructure:"pomerium_debug"`
|
||||
|
@ -141,6 +142,9 @@ type Options struct {
|
|||
// GRPC Service Settings
|
||||
GRPCClientTimeout time.Duration `mapstructure:"grpc_client_timeout"`
|
||||
GRPCClientDNSRoundRobin bool `mapstructure:"grpc_client_dns_roundrobin"`
|
||||
|
||||
// Scoped viper instance
|
||||
viper *viper.Viper
|
||||
}
|
||||
|
||||
var defaultOptions = Options{
|
||||
|
@ -171,41 +175,63 @@ var defaultOptions = Options{
|
|||
GRPCClientDNSRoundRobin: true,
|
||||
}
|
||||
|
||||
// NewOptions returns a minimal options configuration built from default options.
|
||||
// NewOptions creates a new Options struct with only viper initialized
|
||||
func NewOptions() *Options {
|
||||
o := Options{}
|
||||
o.viper = viper.New()
|
||||
return &o
|
||||
}
|
||||
|
||||
// NewDefaultOptions returns an Options struct with defaults set and viper initialized
|
||||
func NewDefaultOptions() *Options {
|
||||
o := defaultOptions
|
||||
o.viper = viper.New()
|
||||
return &o
|
||||
}
|
||||
|
||||
// NewMinimalOptions returns a minimal options configuration built from default options.
|
||||
// Any modifications to the structure should be followed up by a subsequent
|
||||
// call to validate.
|
||||
func NewOptions(authenticateURL, authorizeURL string) (*Options, error) {
|
||||
o := defaultOptions
|
||||
func NewMinimalOptions(authenticateURL, authorizeURL string) (*Options, error) {
|
||||
o := NewDefaultOptions()
|
||||
o.AuthenticateURLString = authenticateURL
|
||||
o.AuthorizeURLString = authorizeURL
|
||||
if err := o.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("internal/config: validation error %s", err)
|
||||
}
|
||||
return &o, nil
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// OptionsFromViper builds the main binary's configuration
|
||||
// options by parsing environmental variables and config file
|
||||
func OptionsFromViper(configFile string) (*Options, error) {
|
||||
// start a copy of the default options
|
||||
o := defaultOptions
|
||||
o := NewDefaultOptions()
|
||||
// New viper instance to save into Options later
|
||||
v := viper.New()
|
||||
// Load up config
|
||||
o.bindEnvs()
|
||||
err := bindEnvs(o, v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to bind options to env vars: %w", err)
|
||||
}
|
||||
|
||||
if configFile != "" {
|
||||
viper.SetConfigFile(configFile)
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
v.SetConfigFile(configFile)
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return nil, fmt.Errorf("internal/config: failed to read config: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := viper.Unmarshal(&o); err != nil {
|
||||
if err := v.Unmarshal(&o); err != nil {
|
||||
return nil, fmt.Errorf("internal/config: failed to unmarshal config: %s", err)
|
||||
}
|
||||
|
||||
o.viper = v
|
||||
|
||||
if err := o.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("internal/config: validation error %s", err)
|
||||
}
|
||||
return &o, nil
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Validate ensures the Options fields are properly formed, present, and hydrated.
|
||||
|
@ -270,7 +296,7 @@ func (o *Options) parsePolicy() error {
|
|||
if err := yaml.Unmarshal(policyBytes, &policies); err != nil {
|
||||
return fmt.Errorf("could not unmarshal policy yaml: %s", err)
|
||||
}
|
||||
} else if err := viper.UnmarshalKey("policy", &policies); err != nil {
|
||||
} else if err := o.viper.UnmarshalKey("policy", &policies); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(policies) != 0 {
|
||||
|
@ -291,7 +317,7 @@ func (o *Options) parseHeaders() error {
|
|||
var headers map[string]string
|
||||
if o.HeadersEnv != "" {
|
||||
// Handle JSON by default via viper
|
||||
if headers = viper.GetStringMapString("HeadersEnv"); len(headers) == 0 {
|
||||
if headers = o.viper.GetStringMapString("HeadersEnv"); len(headers) == 0 {
|
||||
// Try to parse "Key1:Value1,Key2:Value2" syntax
|
||||
headerSlice := strings.Split(o.HeadersEnv, ",")
|
||||
for n := range headerSlice {
|
||||
|
@ -307,29 +333,41 @@ func (o *Options) parseHeaders() error {
|
|||
|
||||
}
|
||||
o.Headers = headers
|
||||
} else if viper.IsSet("headers") {
|
||||
if err := viper.UnmarshalKey("headers", &headers); err != nil {
|
||||
return fmt.Errorf("header %s failed to parse: %s", viper.Get("headers"), err)
|
||||
} else if o.viper.IsSet("headers") {
|
||||
if err := o.viper.UnmarshalKey("headers", &headers); err != nil {
|
||||
return fmt.Errorf("header %s failed to parse: %s", o.viper.Get("headers"), err)
|
||||
}
|
||||
o.Headers = headers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindEnvs makes sure viper binds to each env var based on the mapstructure tag
|
||||
func (o *Options) bindEnvs() {
|
||||
// bindEnvs binds a viper instance to each env var of an Options struct based on the mapstructure tag
|
||||
func bindEnvs(o *Options, v *viper.Viper) error {
|
||||
tagName := `mapstructure`
|
||||
t := reflect.TypeOf(*o)
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
envName := field.Tag.Get(tagName)
|
||||
viper.BindEnv(envName)
|
||||
err := v.BindEnv(envName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to bind field '%s' to env var '%s': %w", field.Name, envName, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Statically bind fields
|
||||
viper.BindEnv("PolicyEnv", "POLICY")
|
||||
viper.BindEnv("HeadersEnv", "HEADERS")
|
||||
err := v.BindEnv("PolicyEnv", "POLICY")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to bind field 'PolicyEnv' to env var 'POLICY': %w", err)
|
||||
}
|
||||
err = v.BindEnv("HeadersEnv", "HEADERS")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to bind field 'HeadersEnv' to env var 'HEADERS': %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OptionsUpdater updates local state based on an Options struct
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue