github.com/macb/etcd@v0.3.1-0.20140227003422-a60481c6b1a0/config/config.go (about) 1 package config 2 3 import ( 4 "flag" 5 "fmt" 6 "io/ioutil" 7 "net" 8 "net/url" 9 "os" 10 "path/filepath" 11 "reflect" 12 "strconv" 13 "strings" 14 15 "github.com/coreos/etcd/third_party/github.com/BurntSushi/toml" 16 17 "github.com/coreos/etcd/log" 18 ustrings "github.com/coreos/etcd/pkg/strings" 19 "github.com/coreos/etcd/server" 20 ) 21 22 // The default location for the etcd configuration file. 23 const DefaultSystemConfigPath = "/etc/etcd/etcd.conf" 24 25 // A lookup of deprecated flags to their new flag name. 26 var newFlagNameLookup = map[string]string{ 27 "C": "peers", 28 "CF": "peers-file", 29 "n": "name", 30 "c": "addr", 31 "cl": "bind-addr", 32 "s": "peer-addr", 33 "sl": "peer-bind-addr", 34 "d": "data-dir", 35 "m": "max-result-buffer", 36 "r": "max-retry-attempts", 37 "maxsize": "max-cluster-size", 38 "clientCAFile": "ca-file", 39 "clientCert": "cert-file", 40 "clientKey": "key-file", 41 "serverCAFile": "peer-ca-file", 42 "serverCert": "peer-cert-file", 43 "serverKey": "peer-key-file", 44 "snapshotCount": "snapshot-count", 45 "peer-heartbeat-timeout": "peer-heartbeat-interval", 46 } 47 48 // Config represents the server configuration. 49 type Config struct { 50 SystemPath string 51 52 Addr string `toml:"addr" env:"ETCD_ADDR"` 53 BindAddr string `toml:"bind_addr" env:"ETCD_BIND_ADDR"` 54 CAFile string `toml:"ca_file" env:"ETCD_CA_FILE"` 55 CertFile string `toml:"cert_file" env:"ETCD_CERT_FILE"` 56 CPUProfileFile string 57 CorsOrigins []string `toml:"cors" env:"ETCD_CORS"` 58 DataDir string `toml:"data_dir" env:"ETCD_DATA_DIR"` 59 Discovery string `toml:"discovery" env:"ETCD_DISCOVERY"` 60 Force bool 61 KeyFile string `toml:"key_file" env:"ETCD_KEY_FILE"` 62 Peers []string `toml:"peers" env:"ETCD_PEERS"` 63 PeersFile string `toml:"peers_file" env:"ETCD_PEERS_FILE"` 64 MaxClusterSize int `toml:"max_cluster_size" env:"ETCD_MAX_CLUSTER_SIZE"` 65 MaxResultBuffer int `toml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"` 66 MaxRetryAttempts int `toml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"` 67 RetryInterval float64 `toml:"retry_interval" env:"ETCD_RETRY_INTERVAL"` 68 Name string `toml:"name" env:"ETCD_NAME"` 69 Snapshot bool `toml:"snapshot" env:"ETCD_SNAPSHOT"` 70 SnapshotCount int `toml:"snapshot_count" env:"ETCD_SNAPSHOTCOUNT"` 71 ShowHelp bool 72 ShowVersion bool 73 Verbose bool `toml:"verbose" env:"ETCD_VERBOSE"` 74 VeryVerbose bool `toml:"very_verbose" env:"ETCD_VERY_VERBOSE"` 75 VeryVeryVerbose bool `toml:"very_very_verbose" env:"ETCD_VERY_VERY_VERBOSE"` 76 Peer struct { 77 Addr string `toml:"addr" env:"ETCD_PEER_ADDR"` 78 BindAddr string `toml:"bind_addr" env:"ETCD_PEER_BIND_ADDR"` 79 CAFile string `toml:"ca_file" env:"ETCD_PEER_CA_FILE"` 80 CertFile string `toml:"cert_file" env:"ETCD_PEER_CERT_FILE"` 81 KeyFile string `toml:"key_file" env:"ETCD_PEER_KEY_FILE"` 82 HeartbeatInterval int `toml:"heartbeat_interval" env:"ETCD_PEER_HEARTBEAT_INTERVAL"` 83 ElectionTimeout int `toml:"election_timeout" env:"ETCD_PEER_ELECTION_TIMEOUT"` 84 } 85 strTrace string `toml:"trace" env:"ETCD_TRACE"` 86 GraphiteHost string `toml:"graphite_host" env:"ETCD_GRAPHITE_HOST"` 87 } 88 89 // New returns a Config initialized with default values. 90 func New() *Config { 91 c := new(Config) 92 c.SystemPath = DefaultSystemConfigPath 93 c.Addr = "127.0.0.1:4001" 94 c.MaxClusterSize = 9 95 c.MaxResultBuffer = 1024 96 c.MaxRetryAttempts = 3 97 c.RetryInterval = 10.0 98 c.Snapshot = true 99 c.SnapshotCount = 10000 100 c.Peer.Addr = "127.0.0.1:7001" 101 c.Peer.HeartbeatInterval = defaultHeartbeatInterval 102 c.Peer.ElectionTimeout = defaultElectionTimeout 103 return c 104 } 105 106 // Loads the configuration from the system config, command line config, 107 // environment variables, and finally command line arguments. 108 func (c *Config) Load(arguments []string) error { 109 var path string 110 f := flag.NewFlagSet("etcd", -1) 111 f.SetOutput(ioutil.Discard) 112 f.StringVar(&path, "config", "", "path to config file") 113 f.Parse(arguments) 114 115 // Load from system file. 116 if err := c.LoadSystemFile(); err != nil { 117 return err 118 } 119 120 // Load from config file specified in arguments. 121 if path != "" { 122 if err := c.LoadFile(path); err != nil { 123 return err 124 } 125 } 126 127 // Load from the environment variables next. 128 if err := c.LoadEnv(); err != nil { 129 return err 130 } 131 132 // Load from command line flags. 133 if err := c.LoadFlags(arguments); err != nil { 134 return err 135 } 136 137 // Loads peers if a peer file was specified. 138 if err := c.LoadPeersFile(); err != nil { 139 return err 140 } 141 142 // Sanitize all the input fields. 143 if err := c.Sanitize(); err != nil { 144 return fmt.Errorf("sanitize: %v", err) 145 } 146 147 // Force remove server configuration if specified. 148 if c.Force { 149 c.Reset() 150 } 151 152 return nil 153 } 154 155 // Loads from the system etcd configuration file if it exists. 156 func (c *Config) LoadSystemFile() error { 157 if _, err := os.Stat(c.SystemPath); os.IsNotExist(err) { 158 return nil 159 } 160 return c.LoadFile(c.SystemPath) 161 } 162 163 // Loads configuration from a file. 164 func (c *Config) LoadFile(path string) error { 165 _, err := toml.DecodeFile(path, &c) 166 return err 167 } 168 169 // LoadEnv loads the configuration via environment variables. 170 func (c *Config) LoadEnv() error { 171 if err := c.loadEnv(c); err != nil { 172 return err 173 } 174 if err := c.loadEnv(&c.Peer); err != nil { 175 return err 176 } 177 return nil 178 } 179 180 func (c *Config) loadEnv(target interface{}) error { 181 value := reflect.Indirect(reflect.ValueOf(target)) 182 typ := value.Type() 183 for i := 0; i < typ.NumField(); i++ { 184 field := typ.Field(i) 185 186 // Retrieve environment variable. 187 v := strings.TrimSpace(os.Getenv(field.Tag.Get("env"))) 188 if v == "" { 189 continue 190 } 191 192 // Set the appropriate type. 193 switch field.Type.Kind() { 194 case reflect.Bool: 195 value.Field(i).SetBool(v != "0" && v != "false") 196 case reflect.Int: 197 newValue, err := strconv.ParseInt(v, 10, 0) 198 if err != nil { 199 return fmt.Errorf("Parse error: %s: %s", field.Tag.Get("env"), err) 200 } 201 value.Field(i).SetInt(newValue) 202 case reflect.String: 203 value.Field(i).SetString(v) 204 case reflect.Slice: 205 value.Field(i).Set(reflect.ValueOf(ustrings.TrimSplit(v, ","))) 206 } 207 } 208 return nil 209 } 210 211 // Loads configuration from command line flags. 212 func (c *Config) LoadFlags(arguments []string) error { 213 var peers, cors, path string 214 215 f := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) 216 f.SetOutput(ioutil.Discard) 217 218 f.BoolVar(&c.ShowHelp, "h", false, "") 219 f.BoolVar(&c.ShowHelp, "help", false, "") 220 f.BoolVar(&c.ShowVersion, "version", false, "") 221 222 f.BoolVar(&c.Force, "f", false, "") 223 f.BoolVar(&c.Force, "force", false, "") 224 225 f.BoolVar(&c.Verbose, "v", c.Verbose, "") 226 f.BoolVar(&c.VeryVerbose, "vv", c.VeryVerbose, "") 227 f.BoolVar(&c.VeryVeryVerbose, "vvv", c.VeryVeryVerbose, "") 228 229 f.StringVar(&peers, "peers", "", "") 230 f.StringVar(&c.PeersFile, "peers-file", c.PeersFile, "") 231 232 f.StringVar(&c.Name, "name", c.Name, "") 233 f.StringVar(&c.Addr, "addr", c.Addr, "") 234 f.StringVar(&c.Discovery, "discovery", c.Discovery, "") 235 f.StringVar(&c.BindAddr, "bind-addr", c.BindAddr, "") 236 f.StringVar(&c.Peer.Addr, "peer-addr", c.Peer.Addr, "") 237 f.StringVar(&c.Peer.BindAddr, "peer-bind-addr", c.Peer.BindAddr, "") 238 239 f.StringVar(&c.CAFile, "ca-file", c.CAFile, "") 240 f.StringVar(&c.CertFile, "cert-file", c.CertFile, "") 241 f.StringVar(&c.KeyFile, "key-file", c.KeyFile, "") 242 243 f.StringVar(&c.Peer.CAFile, "peer-ca-file", c.Peer.CAFile, "") 244 f.StringVar(&c.Peer.CertFile, "peer-cert-file", c.Peer.CertFile, "") 245 f.StringVar(&c.Peer.KeyFile, "peer-key-file", c.Peer.KeyFile, "") 246 247 f.StringVar(&c.DataDir, "data-dir", c.DataDir, "") 248 f.IntVar(&c.MaxResultBuffer, "max-result-buffer", c.MaxResultBuffer, "") 249 f.IntVar(&c.MaxRetryAttempts, "max-retry-attempts", c.MaxRetryAttempts, "") 250 f.Float64Var(&c.RetryInterval, "retry-interval", c.RetryInterval, "") 251 f.IntVar(&c.MaxClusterSize, "max-cluster-size", c.MaxClusterSize, "") 252 f.IntVar(&c.Peer.HeartbeatInterval, "peer-heartbeat-interval", c.Peer.HeartbeatInterval, "") 253 f.IntVar(&c.Peer.ElectionTimeout, "peer-election-timeout", c.Peer.ElectionTimeout, "") 254 255 f.StringVar(&cors, "cors", "", "") 256 257 f.BoolVar(&c.Snapshot, "snapshot", c.Snapshot, "") 258 f.IntVar(&c.SnapshotCount, "snapshot-count", c.SnapshotCount, "") 259 f.StringVar(&c.CPUProfileFile, "cpuprofile", "", "") 260 261 f.StringVar(&c.strTrace, "trace", "", "") 262 f.StringVar(&c.GraphiteHost, "graphite-host", "", "") 263 264 // BEGIN IGNORED FLAGS 265 f.StringVar(&path, "config", "", "") 266 // BEGIN IGNORED FLAGS 267 268 // BEGIN DEPRECATED FLAGS 269 f.StringVar(&peers, "C", "", "(deprecated)") 270 f.StringVar(&c.PeersFile, "CF", c.PeersFile, "(deprecated)") 271 f.StringVar(&c.Name, "n", c.Name, "(deprecated)") 272 f.StringVar(&c.Addr, "c", c.Addr, "(deprecated)") 273 f.StringVar(&c.BindAddr, "cl", c.BindAddr, "(deprecated)") 274 f.StringVar(&c.Peer.Addr, "s", c.Peer.Addr, "(deprecated)") 275 f.StringVar(&c.Peer.BindAddr, "sl", c.Peer.BindAddr, "(deprecated)") 276 f.StringVar(&c.Peer.CAFile, "serverCAFile", c.Peer.CAFile, "(deprecated)") 277 f.StringVar(&c.Peer.CertFile, "serverCert", c.Peer.CertFile, "(deprecated)") 278 f.StringVar(&c.Peer.KeyFile, "serverKey", c.Peer.KeyFile, "(deprecated)") 279 f.StringVar(&c.CAFile, "clientCAFile", c.CAFile, "(deprecated)") 280 f.StringVar(&c.CertFile, "clientCert", c.CertFile, "(deprecated)") 281 f.StringVar(&c.KeyFile, "clientKey", c.KeyFile, "(deprecated)") 282 f.StringVar(&c.DataDir, "d", c.DataDir, "(deprecated)") 283 f.IntVar(&c.MaxResultBuffer, "m", c.MaxResultBuffer, "(deprecated)") 284 f.IntVar(&c.MaxRetryAttempts, "r", c.MaxRetryAttempts, "(deprecated)") 285 f.IntVar(&c.MaxClusterSize, "maxsize", c.MaxClusterSize, "(deprecated)") 286 f.IntVar(&c.SnapshotCount, "snapshotCount", c.SnapshotCount, "(deprecated)") 287 f.IntVar(&c.Peer.HeartbeatInterval, "peer-heartbeat-timeout", c.Peer.HeartbeatInterval, "(deprecated)") 288 // END DEPRECATED FLAGS 289 290 if err := f.Parse(arguments); err != nil { 291 return err 292 } 293 294 // Print deprecation warnings on STDERR. 295 f.Visit(func(f *flag.Flag) { 296 if len(newFlagNameLookup[f.Name]) > 0 { 297 fmt.Fprintf(os.Stderr, "[deprecated] use -%s, not -%s\n", newFlagNameLookup[f.Name], f.Name) 298 } 299 }) 300 301 // Convert some parameters to lists. 302 if peers != "" { 303 c.Peers = ustrings.TrimSplit(peers, ",") 304 } 305 if cors != "" { 306 c.CorsOrigins = ustrings.TrimSplit(cors, ",") 307 } 308 309 return nil 310 } 311 312 // LoadPeersFile loads the peers listed in the peers file. 313 func (c *Config) LoadPeersFile() error { 314 if c.PeersFile == "" { 315 return nil 316 } 317 318 b, err := ioutil.ReadFile(c.PeersFile) 319 if err != nil { 320 return fmt.Errorf("Peers file error: %s", err) 321 } 322 c.Peers = ustrings.TrimSplit(string(b), ",") 323 324 return nil 325 } 326 327 // DataDirFromName sets the data dir from a machine name and issue a warning 328 // that etcd is guessing. 329 func (c *Config) DataDirFromName() { 330 c.DataDir = c.Name + ".etcd" 331 log.Warnf("Using the directory %s as the etcd curation directory because a directory was not specified. ", c.DataDir) 332 333 return 334 } 335 336 // NameFromHostname sets the machine name from the hostname. This is to help 337 // people get started without thinking up a name. 338 func (c *Config) NameFromHostname() { 339 host, err := os.Hostname() 340 if err != nil && host == "" { 341 log.Fatal("Node name required and hostname not set. e.g. '-name=name'") 342 } 343 c.Name = host 344 } 345 346 // Reset removes all server configuration files. 347 func (c *Config) Reset() error { 348 if err := os.RemoveAll(filepath.Join(c.DataDir, "log")); err != nil { 349 return err 350 } 351 if err := os.RemoveAll(filepath.Join(c.DataDir, "conf")); err != nil { 352 return err 353 } 354 if err := os.RemoveAll(filepath.Join(c.DataDir, "snapshot")); err != nil { 355 return err 356 } 357 358 return nil 359 } 360 361 // Sanitize cleans the input fields. 362 func (c *Config) Sanitize() error { 363 var err error 364 365 // Sanitize the URLs first. 366 if c.Addr, err = sanitizeURL(c.Addr, c.EtcdTLSInfo().Scheme()); err != nil { 367 return fmt.Errorf("Advertised URL: %s", err) 368 } 369 if c.BindAddr, err = sanitizeBindAddr(c.BindAddr, c.Addr); err != nil { 370 return fmt.Errorf("Listen Host: %s", err) 371 } 372 if c.Peer.Addr, err = sanitizeURL(c.Peer.Addr, c.PeerTLSInfo().Scheme()); err != nil { 373 return fmt.Errorf("Peer Advertised URL: %s", err) 374 } 375 if c.Peer.BindAddr, err = sanitizeBindAddr(c.Peer.BindAddr, c.Peer.Addr); err != nil { 376 return fmt.Errorf("Peer Listen Host: %s", err) 377 } 378 379 // Only guess the machine name if there is no data dir specified 380 // because the info file should have our name 381 if c.Name == "" && c.DataDir == "" { 382 c.NameFromHostname() 383 } 384 385 if c.DataDir == "" && c.Name != "" && !c.ShowVersion && !c.ShowHelp { 386 c.DataDirFromName() 387 } 388 389 return nil 390 } 391 392 // EtcdTLSInfo retrieves a TLSInfo object for the etcd server 393 func (c *Config) EtcdTLSInfo() server.TLSInfo { 394 return server.TLSInfo{ 395 CAFile: c.CAFile, 396 CertFile: c.CertFile, 397 KeyFile: c.KeyFile, 398 } 399 } 400 401 // PeerRaftInfo retrieves a TLSInfo object for the peer server. 402 func (c *Config) PeerTLSInfo() server.TLSInfo { 403 return server.TLSInfo{ 404 CAFile: c.Peer.CAFile, 405 CertFile: c.Peer.CertFile, 406 KeyFile: c.Peer.KeyFile, 407 } 408 } 409 410 // MetricsBucketName generates the name that should be used for a 411 // corresponding MetricsBucket object 412 func (c *Config) MetricsBucketName() string { 413 return fmt.Sprintf("etcd.%s", c.Name) 414 } 415 416 // Trace determines if any trace-level information should be emitted 417 func (c *Config) Trace() bool { 418 return c.strTrace == "*" 419 } 420 421 // sanitizeURL will cleanup a host string in the format hostname[:port] and 422 // attach a schema. 423 func sanitizeURL(host string, defaultScheme string) (string, error) { 424 // Blank URLs are fine input, just return it 425 if len(host) == 0 { 426 return host, nil 427 } 428 429 p, err := url.Parse(host) 430 if err != nil { 431 return "", err 432 } 433 434 // Make sure the host is in Host:Port format 435 _, _, err = net.SplitHostPort(host) 436 if err != nil { 437 return "", err 438 } 439 440 p = &url.URL{Host: host, Scheme: defaultScheme} 441 return p.String(), nil 442 } 443 444 // sanitizeBindAddr cleans up the BindAddr parameter and appends a port 445 // if necessary based on the advertised port. 446 func sanitizeBindAddr(bindAddr string, addr string) (string, error) { 447 aurl, err := url.Parse(addr) 448 if err != nil { 449 return "", err 450 } 451 452 // If it is a valid host:port simply return with no further checks. 453 bhost, bport, err := net.SplitHostPort(bindAddr) 454 if err == nil && bhost != "" { 455 return bindAddr, nil 456 } 457 458 // SplitHostPort makes the host optional, but we don't want that. 459 if bhost == "" && bport != "" { 460 return "", fmt.Errorf("IP required can't use a port only") 461 } 462 463 // bindAddr doesn't have a port if we reach here so take the port from the 464 // advertised URL. 465 _, aport, err := net.SplitHostPort(aurl.Host) 466 if err != nil { 467 return "", err 468 } 469 470 return net.JoinHostPort(bindAddr, aport), nil 471 }