github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/command/agent/config.go (about)

     1  package agent
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	client "github.com/hashicorp/nomad/client/config"
    17  	"github.com/hashicorp/nomad/nomad"
    18  	"github.com/hashicorp/nomad/nomad/structs/config"
    19  )
    20  
    21  // Config is the configuration for the Nomad agent.
    22  type Config struct {
    23  	// Region is the region this agent is in. Defaults to global.
    24  	Region string `mapstructure:"region"`
    25  
    26  	// Datacenter is the datacenter this agent is in. Defaults to dc1
    27  	Datacenter string `mapstructure:"datacenter"`
    28  
    29  	// NodeName is the name we register as. Defaults to hostname.
    30  	NodeName string `mapstructure:"name"`
    31  
    32  	// DataDir is the directory to store our state in
    33  	DataDir string `mapstructure:"data_dir"`
    34  
    35  	// LogLevel is the level of the logs to putout
    36  	LogLevel string `mapstructure:"log_level"`
    37  
    38  	// BindAddr is the address on which all of nomad's services will
    39  	// be bound. If not specified, this defaults to 127.0.0.1.
    40  	BindAddr string `mapstructure:"bind_addr"`
    41  
    42  	// EnableDebug is used to enable debugging HTTP endpoints
    43  	EnableDebug bool `mapstructure:"enable_debug"`
    44  
    45  	// Ports is used to control the network ports we bind to.
    46  	Ports *Ports `mapstructure:"ports"`
    47  
    48  	// Addresses is used to override the network addresses we bind to.
    49  	Addresses *Addresses `mapstructure:"addresses"`
    50  
    51  	// AdvertiseAddrs is used to control the addresses we advertise.
    52  	AdvertiseAddrs *AdvertiseAddrs `mapstructure:"advertise"`
    53  
    54  	// Client has our client related settings
    55  	Client *ClientConfig `mapstructure:"client"`
    56  
    57  	// Server has our server related settings
    58  	Server *ServerConfig `mapstructure:"server"`
    59  
    60  	// Telemetry is used to configure sending telemetry
    61  	Telemetry *Telemetry `mapstructure:"telemetry"`
    62  
    63  	// LeaveOnInt is used to gracefully leave on the interrupt signal
    64  	LeaveOnInt bool `mapstructure:"leave_on_interrupt"`
    65  
    66  	// LeaveOnTerm is used to gracefully leave on the terminate signal
    67  	LeaveOnTerm bool `mapstructure:"leave_on_terminate"`
    68  
    69  	// EnableSyslog is used to enable sending logs to syslog
    70  	EnableSyslog bool `mapstructure:"enable_syslog"`
    71  
    72  	// SyslogFacility is used to control the syslog facility used.
    73  	SyslogFacility string `mapstructure:"syslog_facility"`
    74  
    75  	// DisableUpdateCheck is used to disable the periodic update
    76  	// and security bulletin checking.
    77  	DisableUpdateCheck bool `mapstructure:"disable_update_check"`
    78  
    79  	// DisableAnonymousSignature is used to disable setting the
    80  	// anonymous signature when doing the update check and looking
    81  	// for security bulletins
    82  	DisableAnonymousSignature bool `mapstructure:"disable_anonymous_signature"`
    83  
    84  	// AtlasConfig is used to configure Atlas
    85  	Atlas *AtlasConfig `mapstructure:"atlas"`
    86  
    87  	// Consul contains the configuration for the Consul Agent and
    88  	// parameters necessary to register services, their checks, and
    89  	// discover the current Nomad servers.
    90  	Consul *config.ConsulConfig `mapstructure:"consul"`
    91  
    92  	// Vault contains the configuration for the Vault Agent and
    93  	// parameters necessary to derive tokens.
    94  	Vault *config.VaultConfig `mapstructure:"vault"`
    95  
    96  	// NomadConfig is used to override the default config.
    97  	// This is largly used for testing purposes.
    98  	NomadConfig *nomad.Config `mapstructure:"-" json:"-"`
    99  
   100  	// ClientConfig is used to override the default config.
   101  	// This is largly used for testing purposes.
   102  	ClientConfig *client.Config `mapstructure:"-" json:"-"`
   103  
   104  	// DevMode is set by the -dev CLI flag.
   105  	DevMode bool `mapstructure:"-"`
   106  
   107  	// Version information is set at compilation time
   108  	Revision          string
   109  	Version           string
   110  	VersionPrerelease string
   111  
   112  	// List of config files that have been loaded (in order)
   113  	Files []string `mapstructure:"-"`
   114  
   115  	// TLSConfig provides TLS related configuration for the Nomad server and
   116  	// client
   117  	TLSConfig *config.TLSConfig `mapstructure:"tls"`
   118  
   119  	// HTTPAPIResponseHeaders allows users to configure the Nomad http agent to
   120  	// set arbritrary headers on API responses
   121  	HTTPAPIResponseHeaders map[string]string `mapstructure:"http_api_response_headers"`
   122  }
   123  
   124  // AtlasConfig is used to enable an parameterize the Atlas integration
   125  type AtlasConfig struct {
   126  	// Infrastructure is the name of the infrastructure
   127  	// we belong to. e.g. hashicorp/stage
   128  	Infrastructure string `mapstructure:"infrastructure"`
   129  
   130  	// Token is our authentication token from Atlas
   131  	Token string `mapstructure:"token" json:"-"`
   132  
   133  	// Join controls if Atlas will attempt to auto-join the node
   134  	// to it's cluster. Requires Atlas integration.
   135  	Join bool `mapstructure:"join"`
   136  
   137  	// Endpoint is the SCADA endpoint used for Atlas integration. If
   138  	// empty, the defaults from the provider are used.
   139  	Endpoint string `mapstructure:"endpoint"`
   140  }
   141  
   142  // ClientConfig is configuration specific to the client mode
   143  type ClientConfig struct {
   144  	// Enabled controls if we are a client
   145  	Enabled bool `mapstructure:"enabled"`
   146  
   147  	// StateDir is the state directory
   148  	StateDir string `mapstructure:"state_dir"`
   149  
   150  	// AllocDir is the directory for storing allocation data
   151  	AllocDir string `mapstructure:"alloc_dir"`
   152  
   153  	// Servers is a list of known server addresses. These are as "host:port"
   154  	Servers []string `mapstructure:"servers"`
   155  
   156  	// NodeClass is used to group the node by class
   157  	NodeClass string `mapstructure:"node_class"`
   158  
   159  	// Options is used for configuration of nomad internals,
   160  	// like fingerprinters and drivers. The format is:
   161  	//
   162  	//  namespace.option = value
   163  	Options map[string]string `mapstructure:"options"`
   164  
   165  	// Metadata associated with the node
   166  	Meta map[string]string `mapstructure:"meta"`
   167  
   168  	// A mapping of directories on the host OS to attempt to embed inside each
   169  	// task's chroot.
   170  	ChrootEnv map[string]string `mapstructure:"chroot_env"`
   171  
   172  	// Interface to use for network fingerprinting
   173  	NetworkInterface string `mapstructure:"network_interface"`
   174  
   175  	// The network link speed to use if it can not be determined dynamically.
   176  	NetworkSpeed int `mapstructure:"network_speed"`
   177  
   178  	// MaxKillTimeout allows capping the user-specifiable KillTimeout.
   179  	MaxKillTimeout string `mapstructure:"max_kill_timeout"`
   180  
   181  	// ClientMaxPort is the upper range of the ports that the client uses for
   182  	// communicating with plugin subsystems
   183  	ClientMaxPort int `mapstructure:"client_max_port"`
   184  
   185  	// ClientMinPort is the lower range of the ports that the client uses for
   186  	// communicating with plugin subsystems
   187  	ClientMinPort int `mapstructure:"client_min_port"`
   188  
   189  	// Reserved is used to reserve resources from being used by Nomad. This can
   190  	// be used to target a certain utilization or to prevent Nomad from using a
   191  	// particular set of ports.
   192  	Reserved *Resources `mapstructure:"reserved"`
   193  }
   194  
   195  // ServerConfig is configuration specific to the server mode
   196  type ServerConfig struct {
   197  	// Enabled controls if we are a server
   198  	Enabled bool `mapstructure:"enabled"`
   199  
   200  	// BootstrapExpect tries to automatically bootstrap the Consul cluster,
   201  	// by withholding peers until enough servers join.
   202  	BootstrapExpect int `mapstructure:"bootstrap_expect"`
   203  
   204  	// DataDir is the directory to store our state in
   205  	DataDir string `mapstructure:"data_dir"`
   206  
   207  	// ProtocolVersion is the protocol version to speak. This must be between
   208  	// ProtocolVersionMin and ProtocolVersionMax.
   209  	ProtocolVersion int `mapstructure:"protocol_version"`
   210  
   211  	// NumSchedulers is the number of scheduler thread that are run.
   212  	// This can be as many as one per core, or zero to disable this server
   213  	// from doing any scheduling work.
   214  	NumSchedulers int `mapstructure:"num_schedulers"`
   215  
   216  	// EnabledSchedulers controls the set of sub-schedulers that are
   217  	// enabled for this server to handle. This will restrict the evaluations
   218  	// that the workers dequeue for processing.
   219  	EnabledSchedulers []string `mapstructure:"enabled_schedulers"`
   220  
   221  	// NodeGCThreshold controls how "old" a node must be to be collected by GC.
   222  	NodeGCThreshold string `mapstructure:"node_gc_threshold"`
   223  
   224  	// HeartbeatGrace is the grace period beyond the TTL to account for network,
   225  	// processing delays and clock skew before marking a node as "down".
   226  	HeartbeatGrace string `mapstructure:"heartbeat_grace"`
   227  
   228  	// StartJoin is a list of addresses to attempt to join when the
   229  	// agent starts. If Serf is unable to communicate with any of these
   230  	// addresses, then the agent will error and exit.
   231  	StartJoin []string `mapstructure:"start_join"`
   232  
   233  	// RetryJoin is a list of addresses to join with retry enabled.
   234  	RetryJoin []string `mapstructure:"retry_join"`
   235  
   236  	// RetryMaxAttempts specifies the maximum number of times to retry joining a
   237  	// host on startup. This is useful for cases where we know the node will be
   238  	// online eventually.
   239  	RetryMaxAttempts int `mapstructure:"retry_max"`
   240  
   241  	// RetryInterval specifies the amount of time to wait in between join
   242  	// attempts on agent start. The minimum allowed value is 1 second and
   243  	// the default is 30s.
   244  	RetryInterval string        `mapstructure:"retry_interval"`
   245  	retryInterval time.Duration `mapstructure:"-"`
   246  
   247  	// RejoinAfterLeave controls our interaction with the cluster after leave.
   248  	// When set to false (default), a leave causes Consul to not rejoin
   249  	// the cluster until an explicit join is received. If this is set to
   250  	// true, we ignore the leave, and rejoin the cluster on start.
   251  	RejoinAfterLeave bool `mapstructure:"rejoin_after_leave"`
   252  
   253  	// Encryption key to use for the Serf communication
   254  	EncryptKey string `mapstructure:"encrypt" json:"-"`
   255  }
   256  
   257  // EncryptBytes returns the encryption key configured.
   258  func (s *ServerConfig) EncryptBytes() ([]byte, error) {
   259  	return base64.StdEncoding.DecodeString(s.EncryptKey)
   260  }
   261  
   262  // Telemetry is the telemetry configuration for the server
   263  type Telemetry struct {
   264  	StatsiteAddr             string        `mapstructure:"statsite_address"`
   265  	StatsdAddr               string        `mapstructure:"statsd_address"`
   266  	DataDogAddr              string        `mapstructure:"datadog_address"`
   267  	DisableHostname          bool          `mapstructure:"disable_hostname"`
   268  	CollectionInterval       string        `mapstructure:"collection_interval"`
   269  	collectionInterval       time.Duration `mapstructure:"-"`
   270  	PublishAllocationMetrics bool          `mapstructure:"publish_allocation_metrics"`
   271  	PublishNodeMetrics       bool          `mapstructure:"publish_node_metrics"`
   272  
   273  	// Circonus: see https://github.com/circonus-labs/circonus-gometrics
   274  	// for more details on the various configuration options.
   275  	// Valid configuration combinations:
   276  	//    - CirconusAPIToken
   277  	//      metric management enabled (search for existing check or create a new one)
   278  	//    - CirconusSubmissionUrl
   279  	//      metric management disabled (use check with specified submission_url,
   280  	//      broker must be using a public SSL certificate)
   281  	//    - CirconusAPIToken + CirconusCheckSubmissionURL
   282  	//      metric management enabled (use check with specified submission_url)
   283  	//    - CirconusAPIToken + CirconusCheckID
   284  	//      metric management enabled (use check with specified id)
   285  
   286  	// CirconusAPIToken is a valid API Token used to create/manage check. If provided,
   287  	// metric management is enabled.
   288  	// Default: none
   289  	CirconusAPIToken string `mapstructure:"circonus_api_token"`
   290  	// CirconusAPIApp is an app name associated with API token.
   291  	// Default: "nomad"
   292  	CirconusAPIApp string `mapstructure:"circonus_api_app"`
   293  	// CirconusAPIURL is the base URL to use for contacting the Circonus API.
   294  	// Default: "https://api.circonus.com/v2"
   295  	CirconusAPIURL string `mapstructure:"circonus_api_url"`
   296  	// CirconusSubmissionInterval is the interval at which metrics are submitted to Circonus.
   297  	// Default: 10s
   298  	CirconusSubmissionInterval string `mapstructure:"circonus_submission_interval"`
   299  	// CirconusCheckSubmissionURL is the check.config.submission_url field from a
   300  	// previously created HTTPTRAP check.
   301  	// Default: none
   302  	CirconusCheckSubmissionURL string `mapstructure:"circonus_submission_url"`
   303  	// CirconusCheckID is the check id (not check bundle id) from a previously created
   304  	// HTTPTRAP check. The numeric portion of the check._cid field.
   305  	// Default: none
   306  	CirconusCheckID string `mapstructure:"circonus_check_id"`
   307  	// CirconusCheckForceMetricActivation will force enabling metrics, as they are encountered,
   308  	// if the metric already exists and is NOT active. If check management is enabled, the default
   309  	// behavior is to add new metrics as they are encoutered. If the metric already exists in the
   310  	// check, it will *NOT* be activated. This setting overrides that behavior.
   311  	// Default: "false"
   312  	CirconusCheckForceMetricActivation string `mapstructure:"circonus_check_force_metric_activation"`
   313  	// CirconusCheckInstanceID serves to uniquely identify the metrics comming from this "instance".
   314  	// It can be used to maintain metric continuity with transient or ephemeral instances as
   315  	// they move around within an infrastructure.
   316  	// Default: hostname:app
   317  	CirconusCheckInstanceID string `mapstructure:"circonus_check_instance_id"`
   318  	// CirconusCheckSearchTag is a special tag which, when coupled with the instance id, helps to
   319  	// narrow down the search results when neither a Submission URL or Check ID is provided.
   320  	// Default: service:app (e.g. service:nomad)
   321  	CirconusCheckSearchTag string `mapstructure:"circonus_check_search_tag"`
   322  	// CirconusCheckTags is a comma separated list of tags to apply to the check. Note that
   323  	// the value of CirconusCheckSearchTag will always be added to the check.
   324  	// Default: none
   325  	CirconusCheckTags string `mapstructure:"circonus_check_tags"`
   326  	// CirconusCheckDisplayName is the name for the check which will be displayed in the Circonus UI.
   327  	// Default: value of CirconusCheckInstanceID
   328  	CirconusCheckDisplayName string `mapstructure:"circonus_check_display_name"`
   329  	// CirconusBrokerID is an explicit broker to use when creating a new check. The numeric portion
   330  	// of broker._cid. If metric management is enabled and neither a Submission URL nor Check ID
   331  	// is provided, an attempt will be made to search for an existing check using Instance ID and
   332  	// Search Tag. If one is not found, a new HTTPTRAP check will be created.
   333  	// Default: use Select Tag if provided, otherwise, a random Enterprise Broker associated
   334  	// with the specified API token or the default Circonus Broker.
   335  	// Default: none
   336  	CirconusBrokerID string `mapstructure:"circonus_broker_id"`
   337  	// CirconusBrokerSelectTag is a special tag which will be used to select a broker when
   338  	// a Broker ID is not provided. The best use of this is to as a hint for which broker
   339  	// should be used based on *where* this particular instance is running.
   340  	// (e.g. a specific geo location or datacenter, dc:sfo)
   341  	// Default: none
   342  	CirconusBrokerSelectTag string `mapstructure:"circonus_broker_select_tag"`
   343  }
   344  
   345  // Ports is used to encapsulate the various ports we bind to for network
   346  // services. If any are not specified then the defaults are used instead.
   347  type Ports struct {
   348  	HTTP int `mapstructure:"http"`
   349  	RPC  int `mapstructure:"rpc"`
   350  	Serf int `mapstructure:"serf"`
   351  }
   352  
   353  // Addresses encapsulates all of the addresses we bind to for various
   354  // network services. Everything is optional and defaults to BindAddr.
   355  type Addresses struct {
   356  	HTTP string `mapstructure:"http"`
   357  	RPC  string `mapstructure:"rpc"`
   358  	Serf string `mapstructure:"serf"`
   359  }
   360  
   361  // AdvertiseAddrs is used to control the addresses we advertise out for
   362  // different network services. Not all network services support an
   363  // advertise address. All are optional and default to BindAddr.
   364  type AdvertiseAddrs struct {
   365  	HTTP string `mapstructure:"http"`
   366  	RPC  string `mapstructure:"rpc"`
   367  	Serf string `mapstructure:"serf"`
   368  }
   369  
   370  type Resources struct {
   371  	CPU                 int    `mapstructure:"cpu"`
   372  	MemoryMB            int    `mapstructure:"memory"`
   373  	DiskMB              int    `mapstructure:"disk"`
   374  	IOPS                int    `mapstructure:"iops"`
   375  	ReservedPorts       string `mapstructure:"reserved_ports"`
   376  	ParsedReservedPorts []int  `mapstructure:"-"`
   377  }
   378  
   379  // ParseReserved expands the ReservedPorts string into a slice of port numbers.
   380  // The supported syntax is comma seperated integers or ranges seperated by
   381  // hyphens. For example, "80,120-150,160"
   382  func (r *Resources) ParseReserved() error {
   383  	parts := strings.Split(r.ReservedPorts, ",")
   384  
   385  	// Hot path the empty case
   386  	if len(parts) == 1 && parts[0] == "" {
   387  		return nil
   388  	}
   389  
   390  	ports := make(map[int]struct{})
   391  	for _, part := range parts {
   392  		part = strings.TrimSpace(part)
   393  		rangeParts := strings.Split(part, "-")
   394  		l := len(rangeParts)
   395  		switch l {
   396  		case 1:
   397  			if val := rangeParts[0]; val == "" {
   398  				return fmt.Errorf("can't specify empty port")
   399  			} else {
   400  				port, err := strconv.Atoi(val)
   401  				if err != nil {
   402  					return err
   403  				}
   404  				ports[port] = struct{}{}
   405  			}
   406  		case 2:
   407  			// We are parsing a range
   408  			start, err := strconv.Atoi(rangeParts[0])
   409  			if err != nil {
   410  				return err
   411  			}
   412  
   413  			end, err := strconv.Atoi(rangeParts[1])
   414  			if err != nil {
   415  				return err
   416  			}
   417  
   418  			if end < start {
   419  				return fmt.Errorf("invalid range: starting value (%v) less than ending (%v) value", end, start)
   420  			}
   421  
   422  			for i := start; i <= end; i++ {
   423  				ports[i] = struct{}{}
   424  			}
   425  		default:
   426  			return fmt.Errorf("can only parse single port numbers or port ranges (ex. 80,100-120,150)")
   427  		}
   428  	}
   429  
   430  	for port := range ports {
   431  		r.ParsedReservedPorts = append(r.ParsedReservedPorts, port)
   432  	}
   433  
   434  	sort.Ints(r.ParsedReservedPorts)
   435  	return nil
   436  }
   437  
   438  // DevConfig is a Config that is used for dev mode of Nomad.
   439  func DevConfig() *Config {
   440  	conf := DefaultConfig()
   441  	conf.LogLevel = "DEBUG"
   442  	conf.Client.Enabled = true
   443  	conf.Server.Enabled = true
   444  	conf.DevMode = true
   445  	conf.EnableDebug = true
   446  	conf.DisableAnonymousSignature = true
   447  	conf.Consul.AutoAdvertise = true
   448  	if runtime.GOOS == "darwin" {
   449  		conf.Client.NetworkInterface = "lo0"
   450  	} else if runtime.GOOS == "linux" {
   451  		conf.Client.NetworkInterface = "lo"
   452  	}
   453  	conf.Client.Options = map[string]string{
   454  		"driver.raw_exec.enable": "true",
   455  	}
   456  	conf.Client.Options = map[string]string{
   457  		"driver.docker.volumes": "true",
   458  	}
   459  
   460  	return conf
   461  }
   462  
   463  // DefaultConfig is a the baseline configuration for Nomad
   464  func DefaultConfig() *Config {
   465  	return &Config{
   466  		LogLevel:   "INFO",
   467  		Region:     "global",
   468  		Datacenter: "dc1",
   469  		BindAddr:   "127.0.0.1",
   470  		Ports: &Ports{
   471  			HTTP: 4646,
   472  			RPC:  4647,
   473  			Serf: 4648,
   474  		},
   475  		Addresses:      &Addresses{},
   476  		AdvertiseAddrs: &AdvertiseAddrs{},
   477  		Atlas:          &AtlasConfig{},
   478  		Consul:         config.DefaultConsulConfig(),
   479  		Vault:          config.DefaultVaultConfig(),
   480  		Client: &ClientConfig{
   481  			Enabled:        false,
   482  			NetworkSpeed:   100,
   483  			MaxKillTimeout: "30s",
   484  			ClientMinPort:  14000,
   485  			ClientMaxPort:  14512,
   486  			Reserved:       &Resources{},
   487  		},
   488  		Server: &ServerConfig{
   489  			Enabled:          false,
   490  			StartJoin:        []string{},
   491  			RetryJoin:        []string{},
   492  			RetryInterval:    "30s",
   493  			RetryMaxAttempts: 0,
   494  		},
   495  		SyslogFacility: "LOCAL0",
   496  		Telemetry: &Telemetry{
   497  			CollectionInterval: "1s",
   498  			collectionInterval: 1 * time.Second,
   499  		},
   500  		TLSConfig: &config.TLSConfig{},
   501  	}
   502  }
   503  
   504  // Listener can be used to get a new listener using a custom bind address.
   505  // If the bind provided address is empty, the BindAddr is used instead.
   506  func (c *Config) Listener(proto, addr string, port int) (net.Listener, error) {
   507  	if addr == "" {
   508  		addr = c.BindAddr
   509  	}
   510  
   511  	// Do our own range check to avoid bugs in package net.
   512  	//
   513  	//   golang.org/issue/11715
   514  	//   golang.org/issue/13447
   515  	//
   516  	// Both of the above bugs were fixed by golang.org/cl/12447 which will be
   517  	// included in Go 1.6. The error returned below is the same as what Go 1.6
   518  	// will return.
   519  	if 0 > port || port > 65535 {
   520  		return nil, &net.OpError{
   521  			Op:  "listen",
   522  			Net: proto,
   523  			Err: &net.AddrError{Err: "invalid port", Addr: fmt.Sprint(port)},
   524  		}
   525  	}
   526  	return net.Listen(proto, fmt.Sprintf("%s:%d", addr, port))
   527  }
   528  
   529  // Merge merges two configurations.
   530  func (c *Config) Merge(b *Config) *Config {
   531  	result := *c
   532  
   533  	if b.Region != "" {
   534  		result.Region = b.Region
   535  	}
   536  	if b.Datacenter != "" {
   537  		result.Datacenter = b.Datacenter
   538  	}
   539  	if b.NodeName != "" {
   540  		result.NodeName = b.NodeName
   541  	}
   542  	if b.DataDir != "" {
   543  		result.DataDir = b.DataDir
   544  	}
   545  	if b.LogLevel != "" {
   546  		result.LogLevel = b.LogLevel
   547  	}
   548  	if b.BindAddr != "" {
   549  		result.BindAddr = b.BindAddr
   550  	}
   551  	if b.EnableDebug {
   552  		result.EnableDebug = true
   553  	}
   554  	if b.LeaveOnInt {
   555  		result.LeaveOnInt = true
   556  	}
   557  	if b.LeaveOnTerm {
   558  		result.LeaveOnTerm = true
   559  	}
   560  	if b.EnableSyslog {
   561  		result.EnableSyslog = true
   562  	}
   563  	if b.SyslogFacility != "" {
   564  		result.SyslogFacility = b.SyslogFacility
   565  	}
   566  	if b.DisableUpdateCheck {
   567  		result.DisableUpdateCheck = true
   568  	}
   569  	if b.DisableAnonymousSignature {
   570  		result.DisableAnonymousSignature = true
   571  	}
   572  
   573  	// Apply the telemetry config
   574  	if result.Telemetry == nil && b.Telemetry != nil {
   575  		telemetry := *b.Telemetry
   576  		result.Telemetry = &telemetry
   577  	} else if b.Telemetry != nil {
   578  		result.Telemetry = result.Telemetry.Merge(b.Telemetry)
   579  	}
   580  
   581  	// Apply the TLS Config
   582  	if result.TLSConfig == nil && b.TLSConfig != nil {
   583  		tlsConfig := *b.TLSConfig
   584  		result.TLSConfig = &tlsConfig
   585  	} else if b.TLSConfig != nil {
   586  		result.TLSConfig = result.TLSConfig.Merge(b.TLSConfig)
   587  	}
   588  
   589  	// Apply the client config
   590  	if result.Client == nil && b.Client != nil {
   591  		client := *b.Client
   592  		result.Client = &client
   593  	} else if b.Client != nil {
   594  		result.Client = result.Client.Merge(b.Client)
   595  	}
   596  
   597  	// Apply the server config
   598  	if result.Server == nil && b.Server != nil {
   599  		server := *b.Server
   600  		result.Server = &server
   601  	} else if b.Server != nil {
   602  		result.Server = result.Server.Merge(b.Server)
   603  	}
   604  
   605  	// Apply the ports config
   606  	if result.Ports == nil && b.Ports != nil {
   607  		ports := *b.Ports
   608  		result.Ports = &ports
   609  	} else if b.Ports != nil {
   610  		result.Ports = result.Ports.Merge(b.Ports)
   611  	}
   612  
   613  	// Apply the address config
   614  	if result.Addresses == nil && b.Addresses != nil {
   615  		addrs := *b.Addresses
   616  		result.Addresses = &addrs
   617  	} else if b.Addresses != nil {
   618  		result.Addresses = result.Addresses.Merge(b.Addresses)
   619  	}
   620  
   621  	// Apply the advertise addrs config
   622  	if result.AdvertiseAddrs == nil && b.AdvertiseAddrs != nil {
   623  		advertise := *b.AdvertiseAddrs
   624  		result.AdvertiseAddrs = &advertise
   625  	} else if b.AdvertiseAddrs != nil {
   626  		result.AdvertiseAddrs = result.AdvertiseAddrs.Merge(b.AdvertiseAddrs)
   627  	}
   628  
   629  	// Apply the Atlas configuration
   630  	if result.Atlas == nil && b.Atlas != nil {
   631  		atlasConfig := *b.Atlas
   632  		result.Atlas = &atlasConfig
   633  	} else if b.Atlas != nil {
   634  		result.Atlas = result.Atlas.Merge(b.Atlas)
   635  	}
   636  
   637  	// Apply the Consul Configuration
   638  	if result.Consul == nil && b.Consul != nil {
   639  		consulConfig := *b.Consul
   640  		result.Consul = &consulConfig
   641  	} else if b.Consul != nil {
   642  		result.Consul = result.Consul.Merge(b.Consul)
   643  	}
   644  
   645  	// Apply the Vault Configuration
   646  	if result.Vault == nil && b.Vault != nil {
   647  		vaultConfig := *b.Vault
   648  		result.Vault = &vaultConfig
   649  	} else if b.Vault != nil {
   650  		result.Vault = result.Vault.Merge(b.Vault)
   651  	}
   652  
   653  	// Merge config files lists
   654  	result.Files = append(result.Files, b.Files...)
   655  
   656  	// Add the http API response header map values
   657  	if result.HTTPAPIResponseHeaders == nil {
   658  		result.HTTPAPIResponseHeaders = make(map[string]string)
   659  	}
   660  	for k, v := range b.HTTPAPIResponseHeaders {
   661  		result.HTTPAPIResponseHeaders[k] = v
   662  	}
   663  
   664  	return &result
   665  }
   666  
   667  // Merge is used to merge two server configs together
   668  func (a *ServerConfig) Merge(b *ServerConfig) *ServerConfig {
   669  	result := *a
   670  
   671  	if b.Enabled {
   672  		result.Enabled = true
   673  	}
   674  	if b.BootstrapExpect > 0 {
   675  		result.BootstrapExpect = b.BootstrapExpect
   676  	}
   677  	if b.DataDir != "" {
   678  		result.DataDir = b.DataDir
   679  	}
   680  	if b.ProtocolVersion != 0 {
   681  		result.ProtocolVersion = b.ProtocolVersion
   682  	}
   683  	if b.NumSchedulers != 0 {
   684  		result.NumSchedulers = b.NumSchedulers
   685  	}
   686  	if b.NodeGCThreshold != "" {
   687  		result.NodeGCThreshold = b.NodeGCThreshold
   688  	}
   689  	if b.HeartbeatGrace != "" {
   690  		result.HeartbeatGrace = b.HeartbeatGrace
   691  	}
   692  	if b.RetryMaxAttempts != 0 {
   693  		result.RetryMaxAttempts = b.RetryMaxAttempts
   694  	}
   695  	if b.RetryInterval != "" {
   696  		result.RetryInterval = b.RetryInterval
   697  		result.retryInterval = b.retryInterval
   698  	}
   699  	if b.RejoinAfterLeave {
   700  		result.RejoinAfterLeave = true
   701  	}
   702  	if b.EncryptKey != "" {
   703  		result.EncryptKey = b.EncryptKey
   704  	}
   705  
   706  	// Add the schedulers
   707  	result.EnabledSchedulers = append(result.EnabledSchedulers, b.EnabledSchedulers...)
   708  
   709  	// Copy the start join addresses
   710  	result.StartJoin = make([]string, 0, len(a.StartJoin)+len(b.StartJoin))
   711  	result.StartJoin = append(result.StartJoin, a.StartJoin...)
   712  	result.StartJoin = append(result.StartJoin, b.StartJoin...)
   713  
   714  	// Copy the retry join addresses
   715  	result.RetryJoin = make([]string, 0, len(a.RetryJoin)+len(b.RetryJoin))
   716  	result.RetryJoin = append(result.RetryJoin, a.RetryJoin...)
   717  	result.RetryJoin = append(result.RetryJoin, b.RetryJoin...)
   718  
   719  	return &result
   720  }
   721  
   722  // Merge is used to merge two client configs together
   723  func (a *ClientConfig) Merge(b *ClientConfig) *ClientConfig {
   724  	result := *a
   725  
   726  	if b.Enabled {
   727  		result.Enabled = true
   728  	}
   729  	if b.StateDir != "" {
   730  		result.StateDir = b.StateDir
   731  	}
   732  	if b.AllocDir != "" {
   733  		result.AllocDir = b.AllocDir
   734  	}
   735  	if b.NodeClass != "" {
   736  		result.NodeClass = b.NodeClass
   737  	}
   738  	if b.NetworkInterface != "" {
   739  		result.NetworkInterface = b.NetworkInterface
   740  	}
   741  	if b.NetworkSpeed != 0 {
   742  		result.NetworkSpeed = b.NetworkSpeed
   743  	}
   744  	if b.MaxKillTimeout != "" {
   745  		result.MaxKillTimeout = b.MaxKillTimeout
   746  	}
   747  	if b.ClientMaxPort != 0 {
   748  		result.ClientMaxPort = b.ClientMaxPort
   749  	}
   750  	if b.ClientMinPort != 0 {
   751  		result.ClientMinPort = b.ClientMinPort
   752  	}
   753  	if b.Reserved != nil {
   754  		result.Reserved = result.Reserved.Merge(b.Reserved)
   755  	}
   756  
   757  	// Add the servers
   758  	result.Servers = append(result.Servers, b.Servers...)
   759  
   760  	// Add the options map values
   761  	if result.Options == nil {
   762  		result.Options = make(map[string]string)
   763  	}
   764  	for k, v := range b.Options {
   765  		result.Options[k] = v
   766  	}
   767  
   768  	// Add the meta map values
   769  	if result.Meta == nil {
   770  		result.Meta = make(map[string]string)
   771  	}
   772  	for k, v := range b.Meta {
   773  		result.Meta[k] = v
   774  	}
   775  
   776  	// Add the chroot_env map values
   777  	if result.ChrootEnv == nil {
   778  		result.ChrootEnv = make(map[string]string)
   779  	}
   780  	for k, v := range b.ChrootEnv {
   781  		result.ChrootEnv[k] = v
   782  	}
   783  
   784  	return &result
   785  }
   786  
   787  // Merge is used to merge two telemetry configs together
   788  func (a *Telemetry) Merge(b *Telemetry) *Telemetry {
   789  	result := *a
   790  
   791  	if b.StatsiteAddr != "" {
   792  		result.StatsiteAddr = b.StatsiteAddr
   793  	}
   794  	if b.StatsdAddr != "" {
   795  		result.StatsdAddr = b.StatsdAddr
   796  	}
   797  	if b.DataDogAddr != "" {
   798  		result.DataDogAddr = b.DataDogAddr
   799  	}
   800  	if b.DisableHostname {
   801  		result.DisableHostname = true
   802  	}
   803  	if b.CollectionInterval != "" {
   804  		result.CollectionInterval = b.CollectionInterval
   805  	}
   806  	if b.collectionInterval != 0 {
   807  		result.collectionInterval = b.collectionInterval
   808  	}
   809  	if b.PublishNodeMetrics {
   810  		result.PublishNodeMetrics = true
   811  	}
   812  	if b.PublishAllocationMetrics {
   813  		result.PublishAllocationMetrics = true
   814  	}
   815  	if b.CirconusAPIToken != "" {
   816  		result.CirconusAPIToken = b.CirconusAPIToken
   817  	}
   818  	if b.CirconusAPIApp != "" {
   819  		result.CirconusAPIApp = b.CirconusAPIApp
   820  	}
   821  	if b.CirconusAPIURL != "" {
   822  		result.CirconusAPIURL = b.CirconusAPIURL
   823  	}
   824  	if b.CirconusCheckSubmissionURL != "" {
   825  		result.CirconusCheckSubmissionURL = b.CirconusCheckSubmissionURL
   826  	}
   827  	if b.CirconusSubmissionInterval != "" {
   828  		result.CirconusSubmissionInterval = b.CirconusSubmissionInterval
   829  	}
   830  	if b.CirconusCheckID != "" {
   831  		result.CirconusCheckID = b.CirconusCheckID
   832  	}
   833  	if b.CirconusCheckForceMetricActivation != "" {
   834  		result.CirconusCheckForceMetricActivation = b.CirconusCheckForceMetricActivation
   835  	}
   836  	if b.CirconusCheckInstanceID != "" {
   837  		result.CirconusCheckInstanceID = b.CirconusCheckInstanceID
   838  	}
   839  	if b.CirconusCheckSearchTag != "" {
   840  		result.CirconusCheckSearchTag = b.CirconusCheckSearchTag
   841  	}
   842  	if b.CirconusCheckTags != "" {
   843  		result.CirconusCheckTags = b.CirconusCheckTags
   844  	}
   845  	if b.CirconusCheckDisplayName != "" {
   846  		result.CirconusCheckDisplayName = b.CirconusCheckDisplayName
   847  	}
   848  	if b.CirconusBrokerID != "" {
   849  		result.CirconusBrokerID = b.CirconusBrokerID
   850  	}
   851  	if b.CirconusBrokerSelectTag != "" {
   852  		result.CirconusBrokerSelectTag = b.CirconusBrokerSelectTag
   853  	}
   854  	return &result
   855  }
   856  
   857  // Merge is used to merge two port configurations.
   858  func (a *Ports) Merge(b *Ports) *Ports {
   859  	result := *a
   860  
   861  	if b.HTTP != 0 {
   862  		result.HTTP = b.HTTP
   863  	}
   864  	if b.RPC != 0 {
   865  		result.RPC = b.RPC
   866  	}
   867  	if b.Serf != 0 {
   868  		result.Serf = b.Serf
   869  	}
   870  	return &result
   871  }
   872  
   873  // Merge is used to merge two address configs together.
   874  func (a *Addresses) Merge(b *Addresses) *Addresses {
   875  	result := *a
   876  
   877  	if b.HTTP != "" {
   878  		result.HTTP = b.HTTP
   879  	}
   880  	if b.RPC != "" {
   881  		result.RPC = b.RPC
   882  	}
   883  	if b.Serf != "" {
   884  		result.Serf = b.Serf
   885  	}
   886  	return &result
   887  }
   888  
   889  // Merge merges two advertise addrs configs together.
   890  func (a *AdvertiseAddrs) Merge(b *AdvertiseAddrs) *AdvertiseAddrs {
   891  	result := *a
   892  
   893  	if b.RPC != "" {
   894  		result.RPC = b.RPC
   895  	}
   896  	if b.Serf != "" {
   897  		result.Serf = b.Serf
   898  	}
   899  	if b.HTTP != "" {
   900  		result.HTTP = b.HTTP
   901  	}
   902  	return &result
   903  }
   904  
   905  // Merge merges two Atlas configurations together.
   906  func (a *AtlasConfig) Merge(b *AtlasConfig) *AtlasConfig {
   907  	result := *a
   908  
   909  	if b.Infrastructure != "" {
   910  		result.Infrastructure = b.Infrastructure
   911  	}
   912  	if b.Token != "" {
   913  		result.Token = b.Token
   914  	}
   915  	if b.Join {
   916  		result.Join = true
   917  	}
   918  	if b.Endpoint != "" {
   919  		result.Endpoint = b.Endpoint
   920  	}
   921  	return &result
   922  }
   923  
   924  func (r *Resources) Merge(b *Resources) *Resources {
   925  	result := *r
   926  	if b.CPU != 0 {
   927  		result.CPU = b.CPU
   928  	}
   929  	if b.MemoryMB != 0 {
   930  		result.MemoryMB = b.MemoryMB
   931  	}
   932  	if b.DiskMB != 0 {
   933  		result.DiskMB = b.DiskMB
   934  	}
   935  	if b.IOPS != 0 {
   936  		result.IOPS = b.IOPS
   937  	}
   938  	if b.ReservedPorts != "" {
   939  		result.ReservedPorts = b.ReservedPorts
   940  	}
   941  	if len(b.ParsedReservedPorts) != 0 {
   942  		result.ParsedReservedPorts = b.ParsedReservedPorts
   943  	}
   944  	return &result
   945  }
   946  
   947  // LoadConfig loads the configuration at the given path, regardless if
   948  // its a file or directory.
   949  func LoadConfig(path string) (*Config, error) {
   950  	fi, err := os.Stat(path)
   951  	if err != nil {
   952  		return nil, err
   953  	}
   954  
   955  	if fi.IsDir() {
   956  		return LoadConfigDir(path)
   957  	}
   958  
   959  	cleaned := filepath.Clean(path)
   960  	config, err := ParseConfigFile(cleaned)
   961  	if err != nil {
   962  		return nil, fmt.Errorf("Error loading %s: %s", cleaned, err)
   963  	}
   964  
   965  	config.Files = append(config.Files, cleaned)
   966  	return config, nil
   967  }
   968  
   969  // LoadConfigDir loads all the configurations in the given directory
   970  // in alphabetical order.
   971  func LoadConfigDir(dir string) (*Config, error) {
   972  	f, err := os.Open(dir)
   973  	if err != nil {
   974  		return nil, err
   975  	}
   976  	defer f.Close()
   977  
   978  	fi, err := f.Stat()
   979  	if err != nil {
   980  		return nil, err
   981  	}
   982  	if !fi.IsDir() {
   983  		return nil, fmt.Errorf(
   984  			"configuration path must be a directory: %s", dir)
   985  	}
   986  
   987  	var files []string
   988  	err = nil
   989  	for err != io.EOF {
   990  		var fis []os.FileInfo
   991  		fis, err = f.Readdir(128)
   992  		if err != nil && err != io.EOF {
   993  			return nil, err
   994  		}
   995  
   996  		for _, fi := range fis {
   997  			// Ignore directories
   998  			if fi.IsDir() {
   999  				continue
  1000  			}
  1001  
  1002  			// Only care about files that are valid to load.
  1003  			name := fi.Name()
  1004  			skip := true
  1005  			if strings.HasSuffix(name, ".hcl") {
  1006  				skip = false
  1007  			} else if strings.HasSuffix(name, ".json") {
  1008  				skip = false
  1009  			}
  1010  			if skip || isTemporaryFile(name) {
  1011  				continue
  1012  			}
  1013  
  1014  			path := filepath.Join(dir, name)
  1015  			files = append(files, path)
  1016  		}
  1017  	}
  1018  
  1019  	// Fast-path if we have no files
  1020  	if len(files) == 0 {
  1021  		return &Config{}, nil
  1022  	}
  1023  
  1024  	sort.Strings(files)
  1025  
  1026  	var result *Config
  1027  	for _, f := range files {
  1028  		config, err := ParseConfigFile(f)
  1029  		if err != nil {
  1030  			return nil, fmt.Errorf("Error loading %s: %s", f, err)
  1031  		}
  1032  		config.Files = append(config.Files, f)
  1033  
  1034  		if result == nil {
  1035  			result = config
  1036  		} else {
  1037  			result = result.Merge(config)
  1038  		}
  1039  	}
  1040  
  1041  	return result, nil
  1042  }
  1043  
  1044  // isTemporaryFile returns true or false depending on whether the
  1045  // provided file name is a temporary file for the following editors:
  1046  // emacs or vim.
  1047  func isTemporaryFile(name string) bool {
  1048  	return strings.HasSuffix(name, "~") || // vim
  1049  		strings.HasPrefix(name, ".#") || // emacs
  1050  		(strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) // emacs
  1051  }