github.com/karlem/nomad@v0.10.2-rc1/command/agent/config_parse.go (about)

     1  package agent
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/hashicorp/hcl"
    14  	"github.com/hashicorp/nomad/nomad/structs/config"
    15  )
    16  
    17  func ParseConfigFile(path string) (*Config, error) {
    18  	// slurp
    19  	var buf bytes.Buffer
    20  	path, err := filepath.Abs(path)
    21  	if err != nil {
    22  		return nil, err
    23  	}
    24  
    25  	f, err := os.Open(path)
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  	defer f.Close()
    30  	if _, err := io.Copy(&buf, f); err != nil {
    31  		return nil, err
    32  	}
    33  
    34  	// parse
    35  	c := &Config{
    36  		Client:    &ClientConfig{ServerJoin: &ServerJoin{}},
    37  		ACL:       &ACLConfig{},
    38  		Server:    &ServerConfig{ServerJoin: &ServerJoin{}},
    39  		Consul:    &config.ConsulConfig{},
    40  		Autopilot: &config.AutopilotConfig{},
    41  		Telemetry: &Telemetry{},
    42  		Vault:     &config.VaultConfig{},
    43  	}
    44  
    45  	err = hcl.Decode(c, buf.String())
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	// convert strings to time.Durations
    51  	err = durations([]td{
    52  		{"gc_interval", &c.Client.GCInterval, &c.Client.GCIntervalHCL},
    53  		{"acl.token_ttl", &c.ACL.TokenTTL, &c.ACL.TokenTTLHCL},
    54  		{"acl.policy_ttl", &c.ACL.PolicyTTL, &c.ACL.PolicyTTLHCL},
    55  		{"client.server_join.retry_interval", &c.Client.ServerJoin.RetryInterval, &c.Client.ServerJoin.RetryIntervalHCL},
    56  		{"server.heartbeat_grace", &c.Server.HeartbeatGrace, &c.Server.HeartbeatGraceHCL},
    57  		{"server.min_heartbeat_ttl", &c.Server.MinHeartbeatTTL, &c.Server.MinHeartbeatTTLHCL},
    58  		{"server.retry_interval", &c.Server.RetryInterval, &c.Server.RetryIntervalHCL},
    59  		{"server.server_join.retry_interval", &c.Server.ServerJoin.RetryInterval, &c.Server.ServerJoin.RetryIntervalHCL},
    60  		{"consul.timeout", &c.Consul.Timeout, &c.Consul.TimeoutHCL},
    61  		{"autopilot.server_stabilization_time", &c.Autopilot.ServerStabilizationTime, &c.Autopilot.ServerStabilizationTimeHCL},
    62  		{"autopilot.last_contact_threshold", &c.Autopilot.LastContactThreshold, &c.Autopilot.LastContactThresholdHCL},
    63  		{"telemetry.collection_interval", &c.Telemetry.collectionInterval, &c.Telemetry.CollectionInterval},
    64  	})
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	// report unexpected keys
    70  	err = extraKeys(c)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	return c, nil
    76  }
    77  
    78  // td holds args for one duration conversion
    79  type td struct {
    80  	path string
    81  	td   *time.Duration
    82  	str  *string
    83  }
    84  
    85  // durations parses the duration strings specified in the config files
    86  // into time.Durations
    87  func durations(xs []td) error {
    88  	for _, x := range xs {
    89  		if x.td != nil && x.str != nil && "" != *x.str {
    90  			d, err := time.ParseDuration(*x.str)
    91  			if err != nil {
    92  				return fmt.Errorf("%s can't parse time duration %s", x.path, *x.str)
    93  			}
    94  
    95  			*x.td = d
    96  		}
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  // removeEqualFold removes the first string that EqualFold matches
   103  func removeEqualFold(xs *[]string, search string) {
   104  	sl := *xs
   105  	for i, x := range sl {
   106  		if strings.EqualFold(x, search) {
   107  			sl = append(sl[:i], sl[i+1:]...)
   108  			if len(sl) == 0 {
   109  				*xs = nil
   110  			} else {
   111  				*xs = sl
   112  			}
   113  			return
   114  		}
   115  	}
   116  }
   117  
   118  func extraKeys(c *Config) error {
   119  	// hcl leaves behind extra keys when parsing JSON. These keys
   120  	// are kept on the top level, taken from slices or the keys of
   121  	// structs contained in slices. Clean up before looking for
   122  	// extra keys.
   123  	for range c.HTTPAPIResponseHeaders {
   124  		removeEqualFold(&c.ExtraKeysHCL, "http_api_response_headers")
   125  	}
   126  
   127  	for _, p := range c.Plugins {
   128  		removeEqualFold(&c.ExtraKeysHCL, p.Name)
   129  		removeEqualFold(&c.ExtraKeysHCL, "config")
   130  		removeEqualFold(&c.ExtraKeysHCL, "plugin")
   131  	}
   132  
   133  	for _, k := range []string{"options", "meta", "chroot_env", "servers", "server_join"} {
   134  		removeEqualFold(&c.ExtraKeysHCL, k)
   135  		removeEqualFold(&c.ExtraKeysHCL, "client")
   136  	}
   137  
   138  	// stats is an unused key, continue to silently ignore it
   139  	removeEqualFold(&c.Client.ExtraKeysHCL, "stats")
   140  
   141  	// Remove HostVolume extra keys
   142  	for _, hv := range c.Client.HostVolumes {
   143  		removeEqualFold(&c.Client.ExtraKeysHCL, hv.Name)
   144  		removeEqualFold(&c.Client.ExtraKeysHCL, "host_volume")
   145  	}
   146  
   147  	for _, k := range []string{"enabled_schedulers", "start_join", "retry_join", "server_join"} {
   148  		removeEqualFold(&c.ExtraKeysHCL, k)
   149  		removeEqualFold(&c.ExtraKeysHCL, "server")
   150  	}
   151  
   152  	for _, k := range []string{"datadog_tags"} {
   153  		removeEqualFold(&c.ExtraKeysHCL, k)
   154  		removeEqualFold(&c.ExtraKeysHCL, "telemetry")
   155  	}
   156  
   157  	return extraKeysImpl([]string{}, reflect.ValueOf(*c))
   158  }
   159  
   160  // extraKeysImpl returns an error if any extraKeys array is not empty
   161  func extraKeysImpl(path []string, val reflect.Value) error {
   162  	stype := val.Type()
   163  	for i := 0; i < stype.NumField(); i++ {
   164  		ftype := stype.Field(i)
   165  		fval := val.Field(i)
   166  
   167  		name := ftype.Name
   168  		prop := ""
   169  		tagSplit(ftype, "hcl", &name, &prop)
   170  
   171  		if fval.Kind() == reflect.Ptr {
   172  			fval = reflect.Indirect(fval)
   173  		}
   174  
   175  		// struct? recurse. add the struct's key to the path
   176  		if fval.Kind() == reflect.Struct {
   177  			err := extraKeysImpl(append([]string{name}, path...), fval)
   178  			if err != nil {
   179  				return err
   180  			}
   181  		}
   182  
   183  		if "unusedKeys" == prop {
   184  			if ks, ok := fval.Interface().([]string); ok && len(ks) != 0 {
   185  				return fmt.Errorf("%s unexpected keys %s",
   186  					strings.Join(path, "."),
   187  					strings.Join(ks, ", "))
   188  			}
   189  		}
   190  	}
   191  	return nil
   192  }
   193  
   194  // tagSplit reads the named tag from the structfield and splits its values into strings
   195  func tagSplit(field reflect.StructField, tagName string, vars ...*string) {
   196  	tag := strings.Split(field.Tag.Get(tagName), ",")
   197  	end := len(tag) - 1
   198  	for i, s := range vars {
   199  		if i > end {
   200  			return
   201  		}
   202  		*s = tag[i]
   203  	}
   204  }