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