github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/command/agent/config.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"net"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/hcl"
    13  	hclobj "github.com/hashicorp/hcl/hcl"
    14  	client "github.com/hashicorp/nomad/client/config"
    15  	"github.com/hashicorp/nomad/nomad"
    16  )
    17  
    18  // Config is the configuration for the Nomad agent.
    19  type Config struct {
    20  	// Region is the region this agent is in. Defaults to global.
    21  	Region string `hcl:"region"`
    22  
    23  	// Datacenter is the datacenter this agent is in. Defaults to dc1
    24  	Datacenter string `hcl:"datacenter"`
    25  
    26  	// NodeName is the name we register as. Defaults to hostname.
    27  	NodeName string `hcl:"name"`
    28  
    29  	// DataDir is the directory to store our state in
    30  	DataDir string `hcl:"data_dir"`
    31  
    32  	// LogLevel is the level of the logs to putout
    33  	LogLevel string `hcl:"log_level"`
    34  
    35  	// BindAddr is the address on which all of nomad's services will
    36  	// be bound. If not specified, this defaults to 127.0.0.1.
    37  	BindAddr string `hcl:"bind_addr"`
    38  
    39  	// EnableDebug is used to enable debugging HTTP endpoints
    40  	EnableDebug bool `hcl:"enable_debug"`
    41  
    42  	// Ports is used to control the network ports we bind to.
    43  	Ports *Ports `hcl:"ports"`
    44  
    45  	// Addresses is used to override the network addresses we bind to.
    46  	Addresses *Addresses `hcl:"addresses"`
    47  
    48  	// AdvertiseAddrs is used to control the addresses we advertise.
    49  	AdvertiseAddrs *AdvertiseAddrs `hcl:"advertise"`
    50  
    51  	// Client has our client related settings
    52  	Client *ClientConfig `hcl:"client"`
    53  
    54  	// Server has our server related settings
    55  	Server *ServerConfig `hcl:"server"`
    56  
    57  	// Telemetry is used to configure sending telemetry
    58  	Telemetry *Telemetry `hcl:"telemetry"`
    59  
    60  	// LeaveOnInt is used to gracefully leave on the interrupt signal
    61  	LeaveOnInt bool `hcl:"leave_on_interrupt"`
    62  
    63  	// LeaveOnTerm is used to gracefully leave on the terminate signal
    64  	LeaveOnTerm bool `hcl:"leave_on_terminate"`
    65  
    66  	// EnableSyslog is used to enable sending logs to syslog
    67  	EnableSyslog bool `hcl:"enable_syslog"`
    68  
    69  	// SyslogFacility is used to control the syslog facility used.
    70  	SyslogFacility string `hcl:"syslog_facility"`
    71  
    72  	// DisableUpdateCheck is used to disable the periodic update
    73  	// and security bulletin checking.
    74  	DisableUpdateCheck bool `hcl:"disable_update_check"`
    75  
    76  	// DisableAnonymousSignature is used to disable setting the
    77  	// anonymous signature when doing the update check and looking
    78  	// for security bulletins
    79  	DisableAnonymousSignature bool `hcl:"disable_anonymous_signature"`
    80  
    81  	// AtlasConfig is used to configure Atlas
    82  	Atlas *AtlasConfig `hcl:"atlas"`
    83  
    84  	// NomadConfig is used to override the default config.
    85  	// This is largly used for testing purposes.
    86  	NomadConfig *nomad.Config `hcl:"-" json:"-"`
    87  
    88  	// ClientConfig is used to override the default config.
    89  	// This is largly used for testing purposes.
    90  	ClientConfig *client.Config `hcl:"-" json:"-"`
    91  
    92  	// DevMode is set by the -dev CLI flag.
    93  	DevMode bool `hcl:"-"`
    94  
    95  	// Version information is set at compilation time
    96  	Revision          string
    97  	Version           string
    98  	VersionPrerelease string
    99  }
   100  
   101  // AtlasConfig is used to enable an parameterize the Atlas integration
   102  type AtlasConfig struct {
   103  	// Infrastructure is the name of the infrastructure
   104  	// we belong to. e.g. hashicorp/stage
   105  	Infrastructure string `hcl:"infrastructure"`
   106  
   107  	// Token is our authentication token from Atlas
   108  	Token string `hcl:"token" json:"-"`
   109  
   110  	// Join controls if Atlas will attempt to auto-join the node
   111  	// to it's cluster. Requires Atlas integration.
   112  	Join bool `hcl:"join"`
   113  
   114  	// Endpoint is the SCADA endpoint used for Atlas integration. If
   115  	// empty, the defaults from the provider are used.
   116  	Endpoint string `hcl:"endpoint"`
   117  }
   118  
   119  // ClientConfig is configuration specific to the client mode
   120  type ClientConfig struct {
   121  	// Enabled controls if we are a client
   122  	Enabled bool `hcl:"enabled"`
   123  
   124  	// StateDir is the state directory
   125  	StateDir string `hcl:"state_dir"`
   126  
   127  	// AllocDir is the directory for storing allocation data
   128  	AllocDir string `hcl:"alloc_dir"`
   129  
   130  	// Servers is a list of known server addresses. These are as "host:port"
   131  	Servers []string `hcl:"servers"`
   132  
   133  	// NodeID is the unique node identifier to use. A UUID is used
   134  	// if not provided, and stored in the data directory
   135  	NodeID string `hcl:"node_id"`
   136  
   137  	// NodeClass is used to group the node by class
   138  	NodeClass string `hcl:"node_class"`
   139  
   140  	// Options is used for configuration of nomad internals,
   141  	// like fingerprinters and drivers. The format is:
   142  	//
   143  	//  namespace.option = value
   144  	Options map[string]string `hcl:"options"`
   145  
   146  	// Metadata associated with the node
   147  	Meta map[string]string `hcl:"meta"`
   148  
   149  	// Interface to use for network fingerprinting
   150  	NetworkInterface string `hcl:"network_interface"`
   151  
   152  	// The network link speed to use if it can not be determined dynamically.
   153  	NetworkSpeed int `hcl:"network_speed"`
   154  }
   155  
   156  // ServerConfig is configuration specific to the server mode
   157  type ServerConfig struct {
   158  	// Enabled controls if we are a server
   159  	Enabled bool `hcl:"enabled"`
   160  
   161  	// BootstrapExpect tries to automatically bootstrap the Consul cluster,
   162  	// by witholding peers until enough servers join.
   163  	BootstrapExpect int `hcl:"bootstrap_expect"`
   164  
   165  	// DataDir is the directory to store our state in
   166  	DataDir string `hcl:"data_dir"`
   167  
   168  	// ProtocolVersion is the protocol version to speak. This must be between
   169  	// ProtocolVersionMin and ProtocolVersionMax.
   170  	ProtocolVersion int `hcl:"protocol_version"`
   171  
   172  	// NumSchedulers is the number of scheduler thread that are run.
   173  	// This can be as many as one per core, or zero to disable this server
   174  	// from doing any scheduling work.
   175  	NumSchedulers int `hcl:"num_schedulers"`
   176  
   177  	// EnabledSchedulers controls the set of sub-schedulers that are
   178  	// enabled for this server to handle. This will restrict the evaluations
   179  	// that the workers dequeue for processing.
   180  	EnabledSchedulers []string `hcl:"enabled_schedulers"`
   181  }
   182  
   183  // Telemetry is the telemetry configuration for the server
   184  type Telemetry struct {
   185  	StatsiteAddr    string `hcl:"statsite_address"`
   186  	StatsdAddr      string `hcl:"statsd_address"`
   187  	DisableHostname bool   `hcl:"disable_hostname"`
   188  }
   189  
   190  // Ports is used to encapsulate the various ports we bind to for network
   191  // services. If any are not specified then the defaults are used instead.
   192  type Ports struct {
   193  	HTTP int `hcl:"http"`
   194  	RPC  int `hcl:"rpc"`
   195  	Serf int `hcl:"serf"`
   196  }
   197  
   198  // Addresses encapsulates all of the addresses we bind to for various
   199  // network services. Everything is optional and defaults to BindAddr.
   200  type Addresses struct {
   201  	HTTP string `hcl:"http"`
   202  	RPC  string `hcl:"rpc"`
   203  	Serf string `hcl:"serf"`
   204  }
   205  
   206  // AdvertiseAddrs is used to control the addresses we advertise out for
   207  // different network services. Not all network services support an
   208  // advertise address. All are optional and default to BindAddr.
   209  type AdvertiseAddrs struct {
   210  	RPC  string `hcl:"rpc"`
   211  	Serf string `hcl:"serf"`
   212  }
   213  
   214  // DevConfig is a Config that is used for dev mode of Nomad.
   215  func DevConfig() *Config {
   216  	conf := DefaultConfig()
   217  	conf.LogLevel = "DEBUG"
   218  	conf.Client.Enabled = true
   219  	conf.Server.Enabled = true
   220  	conf.DevMode = true
   221  	conf.EnableDebug = true
   222  	conf.DisableAnonymousSignature = true
   223  	return conf
   224  }
   225  
   226  // DefaultConfig is a the baseline configuration for Nomad
   227  func DefaultConfig() *Config {
   228  	return &Config{
   229  		LogLevel:   "INFO",
   230  		Region:     "global",
   231  		Datacenter: "dc1",
   232  		BindAddr:   "127.0.0.1",
   233  		Ports: &Ports{
   234  			HTTP: 4646,
   235  			RPC:  4647,
   236  			Serf: 4648,
   237  		},
   238  		Addresses:      &Addresses{},
   239  		AdvertiseAddrs: &AdvertiseAddrs{},
   240  		Atlas:          &AtlasConfig{},
   241  		Client: &ClientConfig{
   242  			Enabled:      false,
   243  			NetworkSpeed: 100,
   244  		},
   245  		Server: &ServerConfig{
   246  			Enabled: false,
   247  		},
   248  	}
   249  }
   250  
   251  // GetListener can be used to get a new listener using a custom bind address.
   252  // If the bind provided address is empty, the BindAddr is used instead.
   253  func (c *Config) Listener(proto, addr string, port int) (net.Listener, error) {
   254  	if addr == "" {
   255  		addr = c.BindAddr
   256  	}
   257  	return net.Listen(proto, fmt.Sprintf("%s:%d", addr, port))
   258  }
   259  
   260  // Merge merges two configurations.
   261  func (a *Config) Merge(b *Config) *Config {
   262  	var result Config = *a
   263  
   264  	if b.Region != "" {
   265  		result.Region = b.Region
   266  	}
   267  	if b.Datacenter != "" {
   268  		result.Datacenter = b.Datacenter
   269  	}
   270  	if b.NodeName != "" {
   271  		result.NodeName = b.NodeName
   272  	}
   273  	if b.DataDir != "" {
   274  		result.DataDir = b.DataDir
   275  	}
   276  	if b.LogLevel != "" {
   277  		result.LogLevel = b.LogLevel
   278  	}
   279  	if b.BindAddr != "" {
   280  		result.BindAddr = b.BindAddr
   281  	}
   282  	if b.EnableDebug {
   283  		result.EnableDebug = true
   284  	}
   285  	if b.LeaveOnInt {
   286  		result.LeaveOnInt = true
   287  	}
   288  	if b.LeaveOnTerm {
   289  		result.LeaveOnTerm = true
   290  	}
   291  	if b.EnableSyslog {
   292  		result.EnableSyslog = true
   293  	}
   294  	if b.SyslogFacility != "" {
   295  		result.SyslogFacility = b.SyslogFacility
   296  	}
   297  	if b.DisableUpdateCheck {
   298  		result.DisableUpdateCheck = true
   299  	}
   300  	if b.DisableAnonymousSignature {
   301  		result.DisableAnonymousSignature = true
   302  	}
   303  
   304  	// Apply the telemetry config
   305  	if result.Telemetry == nil && b.Telemetry != nil {
   306  		telemetry := *b.Telemetry
   307  		result.Telemetry = &telemetry
   308  	} else if b.Telemetry != nil {
   309  		result.Telemetry = result.Telemetry.Merge(b.Telemetry)
   310  	}
   311  
   312  	// Apply the client config
   313  	if result.Client == nil && b.Client != nil {
   314  		client := *b.Client
   315  		result.Client = &client
   316  	} else if b.Client != nil {
   317  		result.Client = result.Client.Merge(b.Client)
   318  	}
   319  
   320  	// Apply the server config
   321  	if result.Server == nil && b.Server != nil {
   322  		server := *b.Server
   323  		result.Server = &server
   324  	} else if b.Server != nil {
   325  		result.Server = result.Server.Merge(b.Server)
   326  	}
   327  
   328  	// Apply the ports config
   329  	if result.Ports == nil && b.Ports != nil {
   330  		ports := *b.Ports
   331  		result.Ports = &ports
   332  	} else if b.Ports != nil {
   333  		result.Ports = result.Ports.Merge(b.Ports)
   334  	}
   335  
   336  	// Apply the address config
   337  	if result.Addresses == nil && b.Addresses != nil {
   338  		addrs := *b.Addresses
   339  		result.Addresses = &addrs
   340  	} else if b.Addresses != nil {
   341  		result.Addresses = result.Addresses.Merge(b.Addresses)
   342  	}
   343  
   344  	// Apply the advertise addrs config
   345  	if result.AdvertiseAddrs == nil && b.AdvertiseAddrs != nil {
   346  		advertise := *b.AdvertiseAddrs
   347  		result.AdvertiseAddrs = &advertise
   348  	} else if b.AdvertiseAddrs != nil {
   349  		result.AdvertiseAddrs = result.AdvertiseAddrs.Merge(b.AdvertiseAddrs)
   350  	}
   351  
   352  	return &result
   353  }
   354  
   355  // Merge is used to merge two server configs together
   356  func (a *ServerConfig) Merge(b *ServerConfig) *ServerConfig {
   357  	var result ServerConfig = *a
   358  
   359  	if b.Enabled {
   360  		result.Enabled = true
   361  	}
   362  	if b.BootstrapExpect > 0 {
   363  		result.BootstrapExpect = b.BootstrapExpect
   364  	}
   365  	if b.DataDir != "" {
   366  		result.DataDir = b.DataDir
   367  	}
   368  	if b.ProtocolVersion != 0 {
   369  		result.ProtocolVersion = b.ProtocolVersion
   370  	}
   371  	if b.NumSchedulers != 0 {
   372  		result.NumSchedulers = b.NumSchedulers
   373  	}
   374  
   375  	// Add the schedulers
   376  	result.EnabledSchedulers = append(result.EnabledSchedulers, b.EnabledSchedulers...)
   377  
   378  	return &result
   379  }
   380  
   381  // Merge is used to merge two client configs together
   382  func (a *ClientConfig) Merge(b *ClientConfig) *ClientConfig {
   383  	var result ClientConfig = *a
   384  
   385  	if b.Enabled {
   386  		result.Enabled = true
   387  	}
   388  	if b.StateDir != "" {
   389  		result.StateDir = b.StateDir
   390  	}
   391  	if b.AllocDir != "" {
   392  		result.AllocDir = b.AllocDir
   393  	}
   394  	if b.NodeID != "" {
   395  		result.NodeID = b.NodeID
   396  	}
   397  	if b.NodeClass != "" {
   398  		result.NodeClass = b.NodeClass
   399  	}
   400  	if b.NetworkInterface != "" {
   401  		result.NetworkInterface = b.NetworkInterface
   402  	}
   403  	if b.NetworkSpeed != 0 {
   404  		result.NetworkSpeed = b.NetworkSpeed
   405  	}
   406  
   407  	// Add the servers
   408  	result.Servers = append(result.Servers, b.Servers...)
   409  
   410  	// Add the options map values
   411  	if result.Options == nil {
   412  		result.Options = make(map[string]string)
   413  	}
   414  	for k, v := range b.Options {
   415  		result.Options[k] = v
   416  	}
   417  
   418  	// Add the meta map values
   419  	if result.Meta == nil {
   420  		result.Meta = make(map[string]string)
   421  	}
   422  	for k, v := range b.Meta {
   423  		result.Meta[k] = v
   424  	}
   425  
   426  	return &result
   427  }
   428  
   429  // Merge is used to merge two telemetry configs together
   430  func (a *Telemetry) Merge(b *Telemetry) *Telemetry {
   431  	var result Telemetry = *a
   432  
   433  	if b.StatsiteAddr != "" {
   434  		result.StatsiteAddr = b.StatsiteAddr
   435  	}
   436  	if b.StatsdAddr != "" {
   437  		result.StatsdAddr = b.StatsdAddr
   438  	}
   439  	if b.DisableHostname {
   440  		result.DisableHostname = true
   441  	}
   442  	return &result
   443  }
   444  
   445  // Merge is used to merge two port configurations.
   446  func (a *Ports) Merge(b *Ports) *Ports {
   447  	var result Ports = *a
   448  
   449  	if b.HTTP != 0 {
   450  		result.HTTP = b.HTTP
   451  	}
   452  	if b.RPC != 0 {
   453  		result.RPC = b.RPC
   454  	}
   455  	if b.Serf != 0 {
   456  		result.Serf = b.Serf
   457  	}
   458  	return &result
   459  }
   460  
   461  // Merge is used to merge two address configs together.
   462  func (a *Addresses) Merge(b *Addresses) *Addresses {
   463  	var result Addresses = *a
   464  
   465  	if b.HTTP != "" {
   466  		result.HTTP = b.HTTP
   467  	}
   468  	if b.RPC != "" {
   469  		result.RPC = b.RPC
   470  	}
   471  	if b.Serf != "" {
   472  		result.Serf = b.Serf
   473  	}
   474  	return &result
   475  }
   476  
   477  // Merge merges two advertise addrs configs together.
   478  func (a *AdvertiseAddrs) Merge(b *AdvertiseAddrs) *AdvertiseAddrs {
   479  	var result AdvertiseAddrs = *a
   480  
   481  	if b.RPC != "" {
   482  		result.RPC = b.RPC
   483  	}
   484  	if b.Serf != "" {
   485  		result.Serf = b.Serf
   486  	}
   487  	return &result
   488  }
   489  
   490  // LoadConfig loads the configuration at the given path, regardless if
   491  // its a file or directory.
   492  func LoadConfig(path string) (*Config, error) {
   493  	fi, err := os.Stat(path)
   494  	if err != nil {
   495  		return nil, err
   496  	}
   497  
   498  	if fi.IsDir() {
   499  		return LoadConfigDir(path)
   500  	} else {
   501  		return LoadConfigFile(path)
   502  	}
   503  }
   504  
   505  // LoadConfigString is used to parse a config string
   506  func LoadConfigString(s string) (*Config, error) {
   507  	// Parse!
   508  	obj, err := hcl.Parse(s)
   509  	if err != nil {
   510  		return nil, err
   511  	}
   512  
   513  	// Start building the result
   514  	var result Config
   515  	if err := hcl.DecodeObject(&result, obj); err != nil {
   516  		return nil, err
   517  	}
   518  
   519  	return &result, nil
   520  }
   521  
   522  // LoadConfigFile loads the configuration from the given file.
   523  func LoadConfigFile(path string) (*Config, error) {
   524  	// Read the file
   525  	d, err := ioutil.ReadFile(path)
   526  	if err != nil {
   527  		return nil, err
   528  	}
   529  	return LoadConfigString(string(d))
   530  }
   531  
   532  func getString(o *hclobj.Object) string {
   533  	if o == nil || o.Type != hclobj.ValueTypeString {
   534  		return ""
   535  	}
   536  
   537  	return o.Value.(string)
   538  }
   539  
   540  // LoadConfigDir loads all the configurations in the given directory
   541  // in alphabetical order.
   542  func LoadConfigDir(dir string) (*Config, error) {
   543  	f, err := os.Open(dir)
   544  	if err != nil {
   545  		return nil, err
   546  	}
   547  	defer f.Close()
   548  
   549  	fi, err := f.Stat()
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  	if !fi.IsDir() {
   554  		return nil, fmt.Errorf(
   555  			"configuration path must be a directory: %s",
   556  			dir)
   557  	}
   558  
   559  	var files []string
   560  	err = nil
   561  	for err != io.EOF {
   562  		var fis []os.FileInfo
   563  		fis, err = f.Readdir(128)
   564  		if err != nil && err != io.EOF {
   565  			return nil, err
   566  		}
   567  
   568  		for _, fi := range fis {
   569  			// Ignore directories
   570  			if fi.IsDir() {
   571  				continue
   572  			}
   573  
   574  			// Only care about files that are valid to load.
   575  			name := fi.Name()
   576  			skip := true
   577  			if strings.HasSuffix(name, ".hcl") {
   578  				skip = false
   579  			} else if strings.HasSuffix(name, ".json") {
   580  				skip = false
   581  			}
   582  			if skip || isTemporaryFile(name) {
   583  				continue
   584  			}
   585  
   586  			path := filepath.Join(dir, name)
   587  			files = append(files, path)
   588  		}
   589  	}
   590  
   591  	// Fast-path if we have no files
   592  	if len(files) == 0 {
   593  		return &Config{}, nil
   594  	}
   595  
   596  	var result *Config
   597  	for _, f := range files {
   598  		config, err := LoadConfigFile(f)
   599  		if err != nil {
   600  			return nil, fmt.Errorf("Error loading %s: %s", f, err)
   601  		}
   602  
   603  		if result == nil {
   604  			result = config
   605  		} else {
   606  			result = result.Merge(config)
   607  		}
   608  	}
   609  
   610  	return result, nil
   611  }
   612  
   613  // isTemporaryFile returns true or false depending on whether the
   614  // provided file name is a temporary file for the following editors:
   615  // emacs or vim.
   616  func isTemporaryFile(name string) bool {
   617  	return strings.HasSuffix(name, "~") || // vim
   618  		strings.HasPrefix(name, ".#") || // emacs
   619  		(strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) // emacs
   620  }