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 }