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