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