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 }