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