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 }