From 6f9c18fe9516e7d2ef90c36f63610f3abb66ef1d Mon Sep 17 00:00:00 2001 From: Jason Wilder Date: Fri, 7 Aug 2015 13:33:34 -0600 Subject: [PATCH] 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 --- cmd/influxd/run/command.go | 5 ++ cmd/influxd/run/config.go | 105 +++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/cmd/influxd/run/command.go b/cmd/influxd/run/command.go index 7961f9dd96..3abda712b7 100644 --- a/cmd/influxd/run/command.go +++ b/cmd/influxd/run/command.go @@ -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 diff --git a/cmd/influxd/run/config.go b/cmd/influxd/run/config.go index 89fc2a6398..dcb3a6de4d 100644 --- a/cmd/influxd/run/config.go +++ b/cmd/influxd/run/config.go @@ -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 +}