github.com/bigcommerce/nomad@v0.9.3-bc/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  	for _, k := range []string{"enabled_schedulers", "start_join", "retry_join", "server_join"} {
   142  		removeEqualFold(&c.ExtraKeysHCL, k)
   143  		removeEqualFold(&c.ExtraKeysHCL, "server")
   144  	}
   145  
   146  	for _, k := range []string{"datadog_tags"} {
   147  		removeEqualFold(&c.ExtraKeysHCL, k)
   148  		removeEqualFold(&c.ExtraKeysHCL, "telemetry")
   149  	}
   150  
   151  	return extraKeysImpl([]string{}, reflect.ValueOf(*c))
   152  }
   153  
   154  // extraKeysImpl returns an error if any extraKeys array is not empty
   155  func extraKeysImpl(path []string, val reflect.Value) error {
   156  	stype := val.Type()
   157  	for i := 0; i < stype.NumField(); i++ {
   158  		ftype := stype.Field(i)
   159  		fval := val.Field(i)
   160  
   161  		name := ftype.Name
   162  		prop := ""
   163  		tagSplit(ftype, "hcl", &name, &prop)
   164  
   165  		if fval.Kind() == reflect.Ptr {
   166  			fval = reflect.Indirect(fval)
   167  		}
   168  
   169  		// struct? recurse. add the struct's key to the path
   170  		if fval.Kind() == reflect.Struct {
   171  			err := extraKeysImpl(append([]string{name}, path...), fval)
   172  			if err != nil {
   173  				return err
   174  			}
   175  		}
   176  
   177  		if "unusedKeys" == prop {
   178  			if ks, ok := fval.Interface().([]string); ok && len(ks) != 0 {
   179  				return fmt.Errorf("%s unexpected keys %s",
   180  					strings.Join(path, "."),
   181  					strings.Join(ks, ", "))
   182  			}
   183  		}
   184  	}
   185  	return nil
   186  }
   187  
   188  // tagSplit reads the named tag from the structfield and splits its values into strings
   189  func tagSplit(field reflect.StructField, tagName string, vars ...*string) {
   190  	tag := strings.Split(field.Tag.Get(tagName), ",")
   191  	end := len(tag) - 1
   192  	for i, s := range vars {
   193  		if i > end {
   194  			return
   195  		}
   196  		*s = tag[i]
   197  	}
   198  }