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  }