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