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 }