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