github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/command/agent/config.go (about) 1 package agent 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "net" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/hashicorp/hcl" 13 hclobj "github.com/hashicorp/hcl/hcl" 14 client "github.com/hashicorp/nomad/client/config" 15 "github.com/hashicorp/nomad/nomad" 16 ) 17 18 // Config is the configuration for the Nomad agent. 19 type Config struct { 20 // Region is the region this agent is in. Defaults to global. 21 Region string `hcl:"region"` 22 23 // Datacenter is the datacenter this agent is in. Defaults to dc1 24 Datacenter string `hcl:"datacenter"` 25 26 // NodeName is the name we register as. Defaults to hostname. 27 NodeName string `hcl:"name"` 28 29 // DataDir is the directory to store our state in 30 DataDir string `hcl:"data_dir"` 31 32 // LogLevel is the level of the logs to putout 33 LogLevel string `hcl:"log_level"` 34 35 // BindAddr is the address on which all of nomad's services will 36 // be bound. If not specified, this defaults to 127.0.0.1. 37 BindAddr string `hcl:"bind_addr"` 38 39 // EnableDebug is used to enable debugging HTTP endpoints 40 EnableDebug bool `hcl:"enable_debug"` 41 42 // Ports is used to control the network ports we bind to. 43 Ports *Ports `hcl:"ports"` 44 45 // Addresses is used to override the network addresses we bind to. 46 Addresses *Addresses `hcl:"addresses"` 47 48 // AdvertiseAddrs is used to control the addresses we advertise. 49 AdvertiseAddrs *AdvertiseAddrs `hcl:"advertise"` 50 51 // Client has our client related settings 52 Client *ClientConfig `hcl:"client"` 53 54 // Server has our server related settings 55 Server *ServerConfig `hcl:"server"` 56 57 // Telemetry is used to configure sending telemetry 58 Telemetry *Telemetry `hcl:"telemetry"` 59 60 // LeaveOnInt is used to gracefully leave on the interrupt signal 61 LeaveOnInt bool `hcl:"leave_on_interrupt"` 62 63 // LeaveOnTerm is used to gracefully leave on the terminate signal 64 LeaveOnTerm bool `hcl:"leave_on_terminate"` 65 66 // EnableSyslog is used to enable sending logs to syslog 67 EnableSyslog bool `hcl:"enable_syslog"` 68 69 // SyslogFacility is used to control the syslog facility used. 70 SyslogFacility string `hcl:"syslog_facility"` 71 72 // DisableUpdateCheck is used to disable the periodic update 73 // and security bulletin checking. 74 DisableUpdateCheck bool `hcl:"disable_update_check"` 75 76 // DisableAnonymousSignature is used to disable setting the 77 // anonymous signature when doing the update check and looking 78 // for security bulletins 79 DisableAnonymousSignature bool `hcl:"disable_anonymous_signature"` 80 81 // AtlasConfig is used to configure Atlas 82 Atlas *AtlasConfig `hcl:"atlas"` 83 84 // NomadConfig is used to override the default config. 85 // This is largly used for testing purposes. 86 NomadConfig *nomad.Config `hcl:"-" json:"-"` 87 88 // ClientConfig is used to override the default config. 89 // This is largly used for testing purposes. 90 ClientConfig *client.Config `hcl:"-" json:"-"` 91 92 // DevMode is set by the -dev CLI flag. 93 DevMode bool `hcl:"-"` 94 95 // Version information is set at compilation time 96 Revision string 97 Version string 98 VersionPrerelease string 99 } 100 101 // AtlasConfig is used to enable an parameterize the Atlas integration 102 type AtlasConfig struct { 103 // Infrastructure is the name of the infrastructure 104 // we belong to. e.g. hashicorp/stage 105 Infrastructure string `hcl:"infrastructure"` 106 107 // Token is our authentication token from Atlas 108 Token string `hcl:"token" json:"-"` 109 110 // Join controls if Atlas will attempt to auto-join the node 111 // to it's cluster. Requires Atlas integration. 112 Join bool `hcl:"join"` 113 114 // Endpoint is the SCADA endpoint used for Atlas integration. If 115 // empty, the defaults from the provider are used. 116 Endpoint string `hcl:"endpoint"` 117 } 118 119 // ClientConfig is configuration specific to the client mode 120 type ClientConfig struct { 121 // Enabled controls if we are a client 122 Enabled bool `hcl:"enabled"` 123 124 // StateDir is the state directory 125 StateDir string `hcl:"state_dir"` 126 127 // AllocDir is the directory for storing allocation data 128 AllocDir string `hcl:"alloc_dir"` 129 130 // Servers is a list of known server addresses. These are as "host:port" 131 Servers []string `hcl:"servers"` 132 133 // NodeID is the unique node identifier to use. A UUID is used 134 // if not provided, and stored in the data directory 135 NodeID string `hcl:"node_id"` 136 137 // NodeClass is used to group the node by class 138 NodeClass string `hcl:"node_class"` 139 140 // Options is used for configuration of nomad internals, 141 // like fingerprinters and drivers. The format is: 142 // 143 // namespace.option = value 144 Options map[string]string `hcl:"options"` 145 146 // Metadata associated with the node 147 Meta map[string]string `hcl:"meta"` 148 149 // Interface to use for network fingerprinting 150 NetworkInterface string `hcl:"network_interface"` 151 152 // The network link speed to use if it can not be determined dynamically. 153 NetworkSpeed int `hcl:"network_speed"` 154 } 155 156 // ServerConfig is configuration specific to the server mode 157 type ServerConfig struct { 158 // Enabled controls if we are a server 159 Enabled bool `hcl:"enabled"` 160 161 // BootstrapExpect tries to automatically bootstrap the Consul cluster, 162 // by witholding peers until enough servers join. 163 BootstrapExpect int `hcl:"bootstrap_expect"` 164 165 // DataDir is the directory to store our state in 166 DataDir string `hcl:"data_dir"` 167 168 // ProtocolVersion is the protocol version to speak. This must be between 169 // ProtocolVersionMin and ProtocolVersionMax. 170 ProtocolVersion int `hcl:"protocol_version"` 171 172 // NumSchedulers is the number of scheduler thread that are run. 173 // This can be as many as one per core, or zero to disable this server 174 // from doing any scheduling work. 175 NumSchedulers int `hcl:"num_schedulers"` 176 177 // EnabledSchedulers controls the set of sub-schedulers that are 178 // enabled for this server to handle. This will restrict the evaluations 179 // that the workers dequeue for processing. 180 EnabledSchedulers []string `hcl:"enabled_schedulers"` 181 } 182 183 // Telemetry is the telemetry configuration for the server 184 type Telemetry struct { 185 StatsiteAddr string `hcl:"statsite_address"` 186 StatsdAddr string `hcl:"statsd_address"` 187 DisableHostname bool `hcl:"disable_hostname"` 188 } 189 190 // Ports is used to encapsulate the various ports we bind to for network 191 // services. If any are not specified then the defaults are used instead. 192 type Ports struct { 193 HTTP int `hcl:"http"` 194 RPC int `hcl:"rpc"` 195 Serf int `hcl:"serf"` 196 } 197 198 // Addresses encapsulates all of the addresses we bind to for various 199 // network services. Everything is optional and defaults to BindAddr. 200 type Addresses struct { 201 HTTP string `hcl:"http"` 202 RPC string `hcl:"rpc"` 203 Serf string `hcl:"serf"` 204 } 205 206 // AdvertiseAddrs is used to control the addresses we advertise out for 207 // different network services. Not all network services support an 208 // advertise address. All are optional and default to BindAddr. 209 type AdvertiseAddrs struct { 210 RPC string `hcl:"rpc"` 211 Serf string `hcl:"serf"` 212 } 213 214 // DevConfig is a Config that is used for dev mode of Nomad. 215 func DevConfig() *Config { 216 conf := DefaultConfig() 217 conf.LogLevel = "DEBUG" 218 conf.Client.Enabled = true 219 conf.Server.Enabled = true 220 conf.DevMode = true 221 conf.EnableDebug = true 222 conf.DisableAnonymousSignature = true 223 return conf 224 } 225 226 // DefaultConfig is a the baseline configuration for Nomad 227 func DefaultConfig() *Config { 228 return &Config{ 229 LogLevel: "INFO", 230 Region: "global", 231 Datacenter: "dc1", 232 BindAddr: "127.0.0.1", 233 Ports: &Ports{ 234 HTTP: 4646, 235 RPC: 4647, 236 Serf: 4648, 237 }, 238 Addresses: &Addresses{}, 239 AdvertiseAddrs: &AdvertiseAddrs{}, 240 Atlas: &AtlasConfig{}, 241 Client: &ClientConfig{ 242 Enabled: false, 243 NetworkSpeed: 100, 244 }, 245 Server: &ServerConfig{ 246 Enabled: false, 247 }, 248 } 249 } 250 251 // GetListener can be used to get a new listener using a custom bind address. 252 // If the bind provided address is empty, the BindAddr is used instead. 253 func (c *Config) Listener(proto, addr string, port int) (net.Listener, error) { 254 if addr == "" { 255 addr = c.BindAddr 256 } 257 return net.Listen(proto, fmt.Sprintf("%s:%d", addr, port)) 258 } 259 260 // Merge merges two configurations. 261 func (a *Config) Merge(b *Config) *Config { 262 var result Config = *a 263 264 if b.Region != "" { 265 result.Region = b.Region 266 } 267 if b.Datacenter != "" { 268 result.Datacenter = b.Datacenter 269 } 270 if b.NodeName != "" { 271 result.NodeName = b.NodeName 272 } 273 if b.DataDir != "" { 274 result.DataDir = b.DataDir 275 } 276 if b.LogLevel != "" { 277 result.LogLevel = b.LogLevel 278 } 279 if b.BindAddr != "" { 280 result.BindAddr = b.BindAddr 281 } 282 if b.EnableDebug { 283 result.EnableDebug = true 284 } 285 if b.LeaveOnInt { 286 result.LeaveOnInt = true 287 } 288 if b.LeaveOnTerm { 289 result.LeaveOnTerm = true 290 } 291 if b.EnableSyslog { 292 result.EnableSyslog = true 293 } 294 if b.SyslogFacility != "" { 295 result.SyslogFacility = b.SyslogFacility 296 } 297 if b.DisableUpdateCheck { 298 result.DisableUpdateCheck = true 299 } 300 if b.DisableAnonymousSignature { 301 result.DisableAnonymousSignature = true 302 } 303 304 // Apply the telemetry config 305 if result.Telemetry == nil && b.Telemetry != nil { 306 telemetry := *b.Telemetry 307 result.Telemetry = &telemetry 308 } else if b.Telemetry != nil { 309 result.Telemetry = result.Telemetry.Merge(b.Telemetry) 310 } 311 312 // Apply the client config 313 if result.Client == nil && b.Client != nil { 314 client := *b.Client 315 result.Client = &client 316 } else if b.Client != nil { 317 result.Client = result.Client.Merge(b.Client) 318 } 319 320 // Apply the server config 321 if result.Server == nil && b.Server != nil { 322 server := *b.Server 323 result.Server = &server 324 } else if b.Server != nil { 325 result.Server = result.Server.Merge(b.Server) 326 } 327 328 // Apply the ports config 329 if result.Ports == nil && b.Ports != nil { 330 ports := *b.Ports 331 result.Ports = &ports 332 } else if b.Ports != nil { 333 result.Ports = result.Ports.Merge(b.Ports) 334 } 335 336 // Apply the address config 337 if result.Addresses == nil && b.Addresses != nil { 338 addrs := *b.Addresses 339 result.Addresses = &addrs 340 } else if b.Addresses != nil { 341 result.Addresses = result.Addresses.Merge(b.Addresses) 342 } 343 344 // Apply the advertise addrs config 345 if result.AdvertiseAddrs == nil && b.AdvertiseAddrs != nil { 346 advertise := *b.AdvertiseAddrs 347 result.AdvertiseAddrs = &advertise 348 } else if b.AdvertiseAddrs != nil { 349 result.AdvertiseAddrs = result.AdvertiseAddrs.Merge(b.AdvertiseAddrs) 350 } 351 352 return &result 353 } 354 355 // Merge is used to merge two server configs together 356 func (a *ServerConfig) Merge(b *ServerConfig) *ServerConfig { 357 var result ServerConfig = *a 358 359 if b.Enabled { 360 result.Enabled = true 361 } 362 if b.BootstrapExpect > 0 { 363 result.BootstrapExpect = b.BootstrapExpect 364 } 365 if b.DataDir != "" { 366 result.DataDir = b.DataDir 367 } 368 if b.ProtocolVersion != 0 { 369 result.ProtocolVersion = b.ProtocolVersion 370 } 371 if b.NumSchedulers != 0 { 372 result.NumSchedulers = b.NumSchedulers 373 } 374 375 // Add the schedulers 376 result.EnabledSchedulers = append(result.EnabledSchedulers, b.EnabledSchedulers...) 377 378 return &result 379 } 380 381 // Merge is used to merge two client configs together 382 func (a *ClientConfig) Merge(b *ClientConfig) *ClientConfig { 383 var result ClientConfig = *a 384 385 if b.Enabled { 386 result.Enabled = true 387 } 388 if b.StateDir != "" { 389 result.StateDir = b.StateDir 390 } 391 if b.AllocDir != "" { 392 result.AllocDir = b.AllocDir 393 } 394 if b.NodeID != "" { 395 result.NodeID = b.NodeID 396 } 397 if b.NodeClass != "" { 398 result.NodeClass = b.NodeClass 399 } 400 if b.NetworkInterface != "" { 401 result.NetworkInterface = b.NetworkInterface 402 } 403 if b.NetworkSpeed != 0 { 404 result.NetworkSpeed = b.NetworkSpeed 405 } 406 407 // Add the servers 408 result.Servers = append(result.Servers, b.Servers...) 409 410 // Add the options map values 411 if result.Options == nil { 412 result.Options = make(map[string]string) 413 } 414 for k, v := range b.Options { 415 result.Options[k] = v 416 } 417 418 // Add the meta map values 419 if result.Meta == nil { 420 result.Meta = make(map[string]string) 421 } 422 for k, v := range b.Meta { 423 result.Meta[k] = v 424 } 425 426 return &result 427 } 428 429 // Merge is used to merge two telemetry configs together 430 func (a *Telemetry) Merge(b *Telemetry) *Telemetry { 431 var result Telemetry = *a 432 433 if b.StatsiteAddr != "" { 434 result.StatsiteAddr = b.StatsiteAddr 435 } 436 if b.StatsdAddr != "" { 437 result.StatsdAddr = b.StatsdAddr 438 } 439 if b.DisableHostname { 440 result.DisableHostname = true 441 } 442 return &result 443 } 444 445 // Merge is used to merge two port configurations. 446 func (a *Ports) Merge(b *Ports) *Ports { 447 var result Ports = *a 448 449 if b.HTTP != 0 { 450 result.HTTP = b.HTTP 451 } 452 if b.RPC != 0 { 453 result.RPC = b.RPC 454 } 455 if b.Serf != 0 { 456 result.Serf = b.Serf 457 } 458 return &result 459 } 460 461 // Merge is used to merge two address configs together. 462 func (a *Addresses) Merge(b *Addresses) *Addresses { 463 var result Addresses = *a 464 465 if b.HTTP != "" { 466 result.HTTP = b.HTTP 467 } 468 if b.RPC != "" { 469 result.RPC = b.RPC 470 } 471 if b.Serf != "" { 472 result.Serf = b.Serf 473 } 474 return &result 475 } 476 477 // Merge merges two advertise addrs configs together. 478 func (a *AdvertiseAddrs) Merge(b *AdvertiseAddrs) *AdvertiseAddrs { 479 var result AdvertiseAddrs = *a 480 481 if b.RPC != "" { 482 result.RPC = b.RPC 483 } 484 if b.Serf != "" { 485 result.Serf = b.Serf 486 } 487 return &result 488 } 489 490 // LoadConfig loads the configuration at the given path, regardless if 491 // its a file or directory. 492 func LoadConfig(path string) (*Config, error) { 493 fi, err := os.Stat(path) 494 if err != nil { 495 return nil, err 496 } 497 498 if fi.IsDir() { 499 return LoadConfigDir(path) 500 } else { 501 return LoadConfigFile(path) 502 } 503 } 504 505 // LoadConfigString is used to parse a config string 506 func LoadConfigString(s string) (*Config, error) { 507 // Parse! 508 obj, err := hcl.Parse(s) 509 if err != nil { 510 return nil, err 511 } 512 513 // Start building the result 514 var result Config 515 if err := hcl.DecodeObject(&result, obj); err != nil { 516 return nil, err 517 } 518 519 return &result, nil 520 } 521 522 // LoadConfigFile loads the configuration from the given file. 523 func LoadConfigFile(path string) (*Config, error) { 524 // Read the file 525 d, err := ioutil.ReadFile(path) 526 if err != nil { 527 return nil, err 528 } 529 return LoadConfigString(string(d)) 530 } 531 532 func getString(o *hclobj.Object) string { 533 if o == nil || o.Type != hclobj.ValueTypeString { 534 return "" 535 } 536 537 return o.Value.(string) 538 } 539 540 // LoadConfigDir loads all the configurations in the given directory 541 // in alphabetical order. 542 func LoadConfigDir(dir string) (*Config, error) { 543 f, err := os.Open(dir) 544 if err != nil { 545 return nil, err 546 } 547 defer f.Close() 548 549 fi, err := f.Stat() 550 if err != nil { 551 return nil, err 552 } 553 if !fi.IsDir() { 554 return nil, fmt.Errorf( 555 "configuration path must be a directory: %s", 556 dir) 557 } 558 559 var files []string 560 err = nil 561 for err != io.EOF { 562 var fis []os.FileInfo 563 fis, err = f.Readdir(128) 564 if err != nil && err != io.EOF { 565 return nil, err 566 } 567 568 for _, fi := range fis { 569 // Ignore directories 570 if fi.IsDir() { 571 continue 572 } 573 574 // Only care about files that are valid to load. 575 name := fi.Name() 576 skip := true 577 if strings.HasSuffix(name, ".hcl") { 578 skip = false 579 } else if strings.HasSuffix(name, ".json") { 580 skip = false 581 } 582 if skip || isTemporaryFile(name) { 583 continue 584 } 585 586 path := filepath.Join(dir, name) 587 files = append(files, path) 588 } 589 } 590 591 // Fast-path if we have no files 592 if len(files) == 0 { 593 return &Config{}, nil 594 } 595 596 var result *Config 597 for _, f := range files { 598 config, err := LoadConfigFile(f) 599 if err != nil { 600 return nil, fmt.Errorf("Error loading %s: %s", f, err) 601 } 602 603 if result == nil { 604 result = config 605 } else { 606 result = result.Merge(config) 607 } 608 } 609 610 return result, nil 611 } 612 613 // isTemporaryFile returns true or false depending on whether the 614 // provided file name is a temporary file for the following editors: 615 // emacs or vim. 616 func isTemporaryFile(name string) bool { 617 return strings.HasSuffix(name, "~") || // vim 618 strings.HasPrefix(name, ".#") || // emacs 619 (strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) // emacs 620 }