Allow overriding of configuration parameters using environment variables

This allows all config variable to be set via environment variables using
a similar naming convention for the toml config.  For example, to change the
HTTP API port using the config, you would set:

[http]
  bind-address = ":8086"

To change it with an environment variable, you would use:

  HTTP_BIND_ADDRESS=":8086" influxd

The section name is used as the env variable prefix and the config key
name is the suffix.  The only change to the config name is that "-" should
be replaced with "_" to avoid shell interpretation issues.

This makes it much easier to configure docker instances within a docker container
or adhoc instances at the command-line.

For slice config sections like graphite, you can currently only override the first
entry since the default config only has 1 entry.  To do that use, GRAPHITE_0 as the
prefix.  You cannot currently add new entries like GRAPHITE_1.  A future PR might
address this issue.

The environment variable values should be the same as the config values.

The order that configuration values are applied is as follows:
* Default config
* Config file
* Environment variables
* Command-line arguments

Fixes #3246
pull/3592/head
Jason Wilder 2015-08-07 13:33:34 -06:00
parent 15ab23c17e
commit 6f9c18fe95
2 changed files with 110 additions and 0 deletions

View File

@ -78,6 +78,11 @@ func (cmd *Command) Run(args ...string) error {
return fmt.Errorf("parse config: %s", err)
}
// Apply any environment variables on top of the parsed config
if err := config.ApplyEnvOverrides(); err != nil {
return fmt.Errorf("apply env config: %v", err)
}
// Override config hostname if specified in the command line args.
if options.Hostname != "" {
config.Meta.Hostname = options.Hostname

View File

@ -6,6 +6,10 @@ import (
"os"
"os/user"
"path/filepath"
"reflect"
"strconv"
"strings"
"time"
"github.com/influxdb/influxdb/cluster"
"github.com/influxdb/influxdb/meta"
@ -112,3 +116,104 @@ func (c *Config) Validate() error {
}
return nil
}
func (c *Config) ApplyEnvOverrides() error {
return c.applyEnvOverrides("", reflect.ValueOf(c))
}
func (c *Config) applyEnvOverrides(prefix string, spec reflect.Value) error {
// If we have a pointer, dereference it
s := spec
if spec.Kind() == reflect.Ptr {
s = spec.Elem()
}
typeOfSpec := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
// Get the toml tag to determine what env var name to use
configName := typeOfSpec.Field(i).Tag.Get("toml")
// Replace hyphens with underscores to avoid issues with shells
configName = strings.Replace(configName, "-", "_", -1)
fieldName := typeOfSpec.Field(i).Name
// Skip any fields that we cannot set
if f.CanSet() || f.Kind() == reflect.Slice {
// Use the upper-case prefix and toml name for the env var
key := strings.ToUpper(configName)
if prefix != "" {
key = strings.ToUpper(fmt.Sprintf("%s_%s", prefix, configName))
}
value := os.Getenv(key)
// If the type is s slice, apply to each using the index as a suffix
// e.g. GRAPHITE_0
if f.Kind() == reflect.Slice || f.Kind() == reflect.Array {
for i := 0; i < f.Len(); i++ {
if err := c.applyEnvOverrides(fmt.Sprintf("%s_%d", key, i), f.Index(i)); err != nil {
return err
}
}
continue
}
// If it's a sub-config, recursively apply
if f.Kind() == reflect.Struct || f.Kind() == reflect.Ptr {
if err := c.applyEnvOverrides(key, f); err != nil {
return err
}
continue
}
// Skip any fields we don't have a value to set
if value == "" {
continue
}
switch f.Kind() {
case reflect.String:
f.SetString(value)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
var intValue int64
// Handle toml.Duration
if f.Type().Name() == "Duration" {
dur, err := time.ParseDuration(value)
if err != nil {
return fmt.Errorf("failed to apply %v to %v using type %v and value '%v'", key, fieldName, f.Type().String(), value)
}
intValue = dur.Nanoseconds()
} else {
var err error
intValue, err = strconv.ParseInt(value, 0, f.Type().Bits())
if err != nil {
return fmt.Errorf("failed to apply %v to %v using type %v and value '%v'", key, fieldName, f.Type().String(), value)
}
}
f.SetInt(intValue)
case reflect.Bool:
boolValue, err := strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("failed to apply %v to %v using type %v and value '%v'", key, fieldName, f.Type().String(), value)
}
f.SetBool(boolValue)
case reflect.Float32, reflect.Float64:
floatValue, err := strconv.ParseFloat(value, f.Type().Bits())
if err != nil {
return fmt.Errorf("failed to apply %v to %v using type %v and value '%v'", key, fieldName, f.Type().String(), value)
}
f.SetFloat(floatValue)
default:
if err := c.applyEnvOverrides(key, f); err != nil {
return err
}
}
}
}
return nil
}