github.com/manicqin/nomad@v0.9.5/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  		{"telemetry.prometheus_push_interval", &c.Telemetry.prometheusPushInterval, &c.Telemetry.PrometheusPushInterval},
    65  	})
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	// report unexpected keys
    71  	err = extraKeys(c)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	return c, nil
    77  }
    78  
    79  // td holds args for one duration conversion
    80  type td struct {
    81  	path string
    82  	td   *time.Duration
    83  	str  *string
    84  }
    85  
    86  // durations parses the duration strings specified in the config files
    87  // into time.Durations
    88  func durations(xs []td) error {
    89  	for _, x := range xs {
    90  		if x.td != nil && x.str != nil && "" != *x.str {
    91  			d, err := time.ParseDuration(*x.str)
    92  			if err != nil {
    93  				return fmt.Errorf("%s can't parse time duration %s", x.path, *x.str)
    94  			}
    95  
    96  			*x.td = d
    97  		}
    98  	}
    99  
   100  	return nil
   101  }
   102  
   103  // removeEqualFold removes the first string that EqualFold matches
   104  func removeEqualFold(xs *[]string, search string) {
   105  	sl := *xs
   106  	for i, x := range sl {
   107  		if strings.EqualFold(x, search) {
   108  			sl = append(sl[:i], sl[i+1:]...)
   109  			if len(sl) == 0 {
   110  				*xs = nil
   111  			} else {
   112  				*xs = sl
   113  			}
   114  			return
   115  		}
   116  	}
   117  }
   118  
   119  func extraKeys(c *Config) error {
   120  	// hcl leaves behind extra keys when parsing JSON. These keys
   121  	// are kept on the top level, taken from slices or the keys of
   122  	// structs contained in slices. Clean up before looking for
   123  	// extra keys.
   124  	for range c.HTTPAPIResponseHeaders {
   125  		removeEqualFold(&c.ExtraKeysHCL, "http_api_response_headers")
   126  	}
   127  
   128  	for _, p := range c.Plugins {
   129  		removeEqualFold(&c.ExtraKeysHCL, p.Name)
   130  		removeEqualFold(&c.ExtraKeysHCL, "config")
   131  		removeEqualFold(&c.ExtraKeysHCL, "plugin")
   132  	}
   133  
   134  	for _, k := range []string{"options", "meta", "chroot_env", "servers", "server_join"} {
   135  		removeEqualFold(&c.ExtraKeysHCL, k)
   136  		removeEqualFold(&c.ExtraKeysHCL, "client")
   137  	}
   138  
   139  	// stats is an unused key, continue to silently ignore it
   140  	removeEqualFold(&c.Client.ExtraKeysHCL, "stats")
   141  
   142  	// Remove HostVolume extra keys
   143  	for _, hv := range c.Client.HostVolumes {
   144  		removeEqualFold(&c.Client.ExtraKeysHCL, hv.Name)
   145  		removeEqualFold(&c.Client.ExtraKeysHCL, "host_volume")
   146  	}
   147  
   148  	for _, k := range []string{"enabled_schedulers", "start_join", "retry_join", "server_join"} {
   149  		removeEqualFold(&c.ExtraKeysHCL, k)
   150  		removeEqualFold(&c.ExtraKeysHCL, "server")
   151  	}
   152  
   153  	for _, k := range []string{"datadog_tags"} {
   154  		removeEqualFold(&c.ExtraKeysHCL, k)
   155  		removeEqualFold(&c.ExtraKeysHCL, "telemetry")
   156  	}
   157  
   158  	return extraKeysImpl([]string{}, reflect.ValueOf(*c))
   159  }
   160  
   161  // extraKeysImpl returns an error if any extraKeys array is not empty
   162  func extraKeysImpl(path []string, val reflect.Value) error {
   163  	stype := val.Type()
   164  	for i := 0; i < stype.NumField(); i++ {
   165  		ftype := stype.Field(i)
   166  		fval := val.Field(i)
   167  
   168  		name := ftype.Name
   169  		prop := ""
   170  		tagSplit(ftype, "hcl", &name, &prop)
   171  
   172  		if fval.Kind() == reflect.Ptr {
   173  			fval = reflect.Indirect(fval)
   174  		}
   175  
   176  		// struct? recurse. add the struct's key to the path
   177  		if fval.Kind() == reflect.Struct {
   178  			err := extraKeysImpl(append([]string{name}, path...), fval)
   179  			if err != nil {
   180  				return err
   181  			}
   182  		}
   183  
   184  		if "unusedKeys" == prop {
   185  			if ks, ok := fval.Interface().([]string); ok && len(ks) != 0 {
   186  				return fmt.Errorf("%s unexpected keys %s",
   187  					strings.Join(path, "."),
   188  					strings.Join(ks, ", "))
   189  			}
   190  		}
   191  	}
   192  	return nil
   193  }
   194  
   195  // tagSplit reads the named tag from the structfield and splits its values into strings
   196  func tagSplit(field reflect.StructField, tagName string, vars ...*string) {
   197  	tag := strings.Split(field.Tag.Get(tagName), ",")
   198  	end := len(tag) - 1
   199  	for i, s := range vars {
   200  		if i > end {
   201  			return
   202  		}
   203  		*s = tag[i]
   204  	}
   205  }