1package api 2 3import ( 4 "fmt" 5 "time" 6) 7 8// CheckRestart describes if and when a task should be restarted based on 9// failing health checks. 10type CheckRestart struct { 11 Limit int `mapstructure:"limit" hcl:"limit,optional"` 12 Grace *time.Duration `mapstructure:"grace" hcl:"grace,optional"` 13 IgnoreWarnings bool `mapstructure:"ignore_warnings" hcl:"ignore_warnings,optional"` 14} 15 16// Canonicalize CheckRestart fields if not nil. 17func (c *CheckRestart) Canonicalize() { 18 if c == nil { 19 return 20 } 21 22 if c.Grace == nil { 23 c.Grace = timeToPtr(1 * time.Second) 24 } 25} 26 27// Copy returns a copy of CheckRestart or nil if unset. 28func (c *CheckRestart) Copy() *CheckRestart { 29 if c == nil { 30 return nil 31 } 32 33 nc := new(CheckRestart) 34 nc.Limit = c.Limit 35 if c.Grace != nil { 36 g := *c.Grace 37 nc.Grace = &g 38 } 39 nc.IgnoreWarnings = c.IgnoreWarnings 40 return nc 41} 42 43// Merge values from other CheckRestart over default values on this 44// CheckRestart and return merged copy. 45func (c *CheckRestart) Merge(o *CheckRestart) *CheckRestart { 46 if c == nil { 47 // Just return other 48 return o 49 } 50 51 nc := c.Copy() 52 53 if o == nil { 54 // Nothing to merge 55 return nc 56 } 57 58 if o.Limit > 0 { 59 nc.Limit = o.Limit 60 } 61 62 if o.Grace != nil { 63 nc.Grace = o.Grace 64 } 65 66 if o.IgnoreWarnings { 67 nc.IgnoreWarnings = o.IgnoreWarnings 68 } 69 70 return nc 71} 72 73// ServiceCheck represents the consul health check that Nomad registers. 74type ServiceCheck struct { 75 //FIXME Id is unused. Remove? 76 Id string `hcl:"id,optional"` 77 Name string `hcl:"name,optional"` 78 Type string `hcl:"type,optional"` 79 Command string `hcl:"command,optional"` 80 Args []string `hcl:"args,optional"` 81 Path string `hcl:"path,optional"` 82 Protocol string `hcl:"protocol,optional"` 83 PortLabel string `mapstructure:"port" hcl:"port,optional"` 84 Expose bool `hcl:"expose,optional"` 85 AddressMode string `mapstructure:"address_mode" hcl:"address_mode,optional"` 86 Interval time.Duration `hcl:"interval,optional"` 87 Timeout time.Duration `hcl:"timeout,optional"` 88 InitialStatus string `mapstructure:"initial_status" hcl:"initial_status,optional"` 89 TLSSkipVerify bool `mapstructure:"tls_skip_verify" hcl:"tls_skip_verify,optional"` 90 Header map[string][]string `hcl:"header,block"` 91 Method string `hcl:"method,optional"` 92 CheckRestart *CheckRestart `mapstructure:"check_restart" hcl:"check_restart,block"` 93 GRPCService string `mapstructure:"grpc_service" hcl:"grpc_service,optional"` 94 GRPCUseTLS bool `mapstructure:"grpc_use_tls" hcl:"grpc_use_tls,optional"` 95 TaskName string `mapstructure:"task" hcl:"task,optional"` 96 SuccessBeforePassing int `mapstructure:"success_before_passing" hcl:"success_before_passing,optional"` 97 FailuresBeforeCritical int `mapstructure:"failures_before_critical" hcl:"failures_before_critical,optional"` 98 Body string `hcl:"body,optional"` 99 OnUpdate string `mapstructure:"on_update" hcl:"on_update,optional"` 100} 101 102// Service represents a Consul service definition. 103type Service struct { 104 //FIXME Id is unused. Remove? 105 Id string `hcl:"id,optional"` 106 Name string `hcl:"name,optional"` 107 Tags []string `hcl:"tags,optional"` 108 CanaryTags []string `mapstructure:"canary_tags" hcl:"canary_tags,optional"` 109 EnableTagOverride bool `mapstructure:"enable_tag_override" hcl:"enable_tag_override,optional"` 110 PortLabel string `mapstructure:"port" hcl:"port,optional"` 111 AddressMode string `mapstructure:"address_mode" hcl:"address_mode,optional"` 112 Checks []ServiceCheck `hcl:"check,block"` 113 CheckRestart *CheckRestart `mapstructure:"check_restart" hcl:"check_restart,block"` 114 Connect *ConsulConnect `hcl:"connect,block"` 115 Meta map[string]string `hcl:"meta,block"` 116 CanaryMeta map[string]string `hcl:"canary_meta,block"` 117 TaskName string `mapstructure:"task" hcl:"task,optional"` 118 OnUpdate string `mapstructure:"on_update" hcl:"on_update,optional"` 119} 120 121const ( 122 OnUpdateRequireHealthy = "require_healthy" 123 OnUpdateIgnoreWarn = "ignore_warnings" 124 OnUpdateIgnore = "ignore" 125) 126 127// Canonicalize the Service by ensuring its name and address mode are set. Task 128// will be nil for group services. 129func (s *Service) Canonicalize(t *Task, tg *TaskGroup, job *Job) { 130 if s.Name == "" { 131 if t != nil { 132 s.Name = fmt.Sprintf("%s-%s-%s", *job.Name, *tg.Name, t.Name) 133 } else { 134 s.Name = fmt.Sprintf("%s-%s", *job.Name, *tg.Name) 135 } 136 } 137 138 // Default to AddressModeAuto 139 if s.AddressMode == "" { 140 s.AddressMode = "auto" 141 } 142 143 // Default to OnUpdateRequireHealthy 144 if s.OnUpdate == "" { 145 s.OnUpdate = OnUpdateRequireHealthy 146 } 147 148 s.Connect.Canonicalize() 149 150 // Canonicalize CheckRestart on Checks and merge Service.CheckRestart 151 // into each check. 152 for i, check := range s.Checks { 153 s.Checks[i].CheckRestart = s.CheckRestart.Merge(check.CheckRestart) 154 s.Checks[i].CheckRestart.Canonicalize() 155 156 if s.Checks[i].SuccessBeforePassing < 0 { 157 s.Checks[i].SuccessBeforePassing = 0 158 } 159 160 if s.Checks[i].FailuresBeforeCritical < 0 { 161 s.Checks[i].FailuresBeforeCritical = 0 162 } 163 164 // Inhert Service 165 if s.Checks[i].OnUpdate == "" { 166 s.Checks[i].OnUpdate = s.OnUpdate 167 } 168 } 169} 170 171// ConsulConnect represents a Consul Connect jobspec stanza. 172type ConsulConnect struct { 173 Native bool `hcl:"native,optional"` 174 Gateway *ConsulGateway `hcl:"gateway,block"` 175 SidecarService *ConsulSidecarService `mapstructure:"sidecar_service" hcl:"sidecar_service,block"` 176 SidecarTask *SidecarTask `mapstructure:"sidecar_task" hcl:"sidecar_task,block"` 177} 178 179func (cc *ConsulConnect) Canonicalize() { 180 if cc == nil { 181 return 182 } 183 184 cc.SidecarService.Canonicalize() 185 cc.SidecarTask.Canonicalize() 186 cc.Gateway.Canonicalize() 187} 188 189// ConsulSidecarService represents a Consul Connect SidecarService jobspec 190// stanza. 191type ConsulSidecarService struct { 192 Tags []string `hcl:"tags,optional"` 193 Port string `hcl:"port,optional"` 194 Proxy *ConsulProxy `hcl:"proxy,block"` 195 DisableDefaultTCPCheck bool `mapstructure:"disable_default_tcp_check" hcl:"disable_default_tcp_check,optional"` 196} 197 198func (css *ConsulSidecarService) Canonicalize() { 199 if css == nil { 200 return 201 } 202 203 if len(css.Tags) == 0 { 204 css.Tags = nil 205 } 206 207 css.Proxy.Canonicalize() 208} 209 210// SidecarTask represents a subset of Task fields that can be set to override 211// the fields of the Task generated for the sidecar 212type SidecarTask struct { 213 Name string `hcl:"name,optional"` 214 Driver string `hcl:"driver,optional"` 215 User string `hcl:"user,optional"` 216 Config map[string]interface{} `hcl:"config,block"` 217 Env map[string]string `hcl:"env,block"` 218 Resources *Resources `hcl:"resources,block"` 219 Meta map[string]string `hcl:"meta,block"` 220 KillTimeout *time.Duration `mapstructure:"kill_timeout" hcl:"kill_timeout,optional"` 221 LogConfig *LogConfig `mapstructure:"logs" hcl:"logs,block"` 222 ShutdownDelay *time.Duration `mapstructure:"shutdown_delay" hcl:"shutdown_delay,optional"` 223 KillSignal string `mapstructure:"kill_signal" hcl:"kill_signal,optional"` 224} 225 226func (st *SidecarTask) Canonicalize() { 227 if st == nil { 228 return 229 } 230 231 if len(st.Config) == 0 { 232 st.Config = nil 233 } 234 235 if len(st.Env) == 0 { 236 st.Env = nil 237 } 238 239 if st.Resources == nil { 240 st.Resources = DefaultResources() 241 } else { 242 st.Resources.Canonicalize() 243 } 244 245 if st.LogConfig == nil { 246 st.LogConfig = DefaultLogConfig() 247 } else { 248 st.LogConfig.Canonicalize() 249 } 250 251 if len(st.Meta) == 0 { 252 st.Meta = nil 253 } 254 255 if st.KillTimeout == nil { 256 st.KillTimeout = timeToPtr(5 * time.Second) 257 } 258 259 if st.ShutdownDelay == nil { 260 st.ShutdownDelay = timeToPtr(0) 261 } 262} 263 264// ConsulProxy represents a Consul Connect sidecar proxy jobspec stanza. 265type ConsulProxy struct { 266 LocalServiceAddress string `mapstructure:"local_service_address" hcl:"local_service_address,optional"` 267 LocalServicePort int `mapstructure:"local_service_port" hcl:"local_service_port,optional"` 268 ExposeConfig *ConsulExposeConfig `mapstructure:"expose" hcl:"expose,block"` 269 Upstreams []*ConsulUpstream `hcl:"upstreams,block"` 270 Config map[string]interface{} `hcl:"config,block"` 271} 272 273func (cp *ConsulProxy) Canonicalize() { 274 if cp == nil { 275 return 276 } 277 278 cp.ExposeConfig.Canonicalize() 279 280 if len(cp.Upstreams) == 0 { 281 cp.Upstreams = nil 282 } 283 284 if len(cp.Config) == 0 { 285 cp.Config = nil 286 } 287} 288 289// ConsulUpstream represents a Consul Connect upstream jobspec stanza. 290type ConsulUpstream struct { 291 DestinationName string `mapstructure:"destination_name" hcl:"destination_name,optional"` 292 LocalBindPort int `mapstructure:"local_bind_port" hcl:"local_bind_port,optional"` 293 Datacenter string `mapstructure:"datacenter" hcl:"datacenter,optional"` 294 LocalBindAddress string `mapstructure:"local_bind_address" hcl:"local_bind_address,optional"` 295} 296 297type ConsulExposeConfig struct { 298 Path []*ConsulExposePath `mapstructure:"path" hcl:"path,block"` 299} 300 301func (cec *ConsulExposeConfig) Canonicalize() { 302 if cec == nil { 303 return 304 } 305 306 if len(cec.Path) == 0 { 307 cec.Path = nil 308 } 309} 310 311type ConsulExposePath struct { 312 Path string `hcl:"path,optional"` 313 Protocol string `hcl:"protocol,optional"` 314 LocalPathPort int `mapstructure:"local_path_port" hcl:"local_path_port,optional"` 315 ListenerPort string `mapstructure:"listener_port" hcl:"listener_port,optional"` 316} 317 318// ConsulGateway is used to configure one of the Consul Connect Gateway types. 319type ConsulGateway struct { 320 // Proxy is used to configure the Envoy instance acting as the gateway. 321 Proxy *ConsulGatewayProxy `hcl:"proxy,block"` 322 323 // Ingress represents the Consul Configuration Entry for an Ingress Gateway. 324 Ingress *ConsulIngressConfigEntry `hcl:"ingress,block"` 325 326 // Terminating represents the Consul Configuration Entry for a Terminating Gateway. 327 Terminating *ConsulTerminatingConfigEntry `hcl:"terminating,block"` 328 329 // Mesh is not yet supported. 330 // Mesh *ConsulMeshConfigEntry 331} 332 333func (g *ConsulGateway) Canonicalize() { 334 if g == nil { 335 return 336 } 337 g.Proxy.Canonicalize() 338 g.Ingress.Canonicalize() 339 g.Terminating.Canonicalize() 340} 341 342func (g *ConsulGateway) Copy() *ConsulGateway { 343 if g == nil { 344 return nil 345 } 346 347 return &ConsulGateway{ 348 Proxy: g.Proxy.Copy(), 349 Ingress: g.Ingress.Copy(), 350 Terminating: g.Terminating.Copy(), 351 } 352} 353 354type ConsulGatewayBindAddress struct { 355 Name string `hcl:",label"` 356 Address string `mapstructure:"address" hcl:"address,optional"` 357 Port int `mapstructure:"port" hcl:"port,optional"` 358} 359 360var ( 361 // defaultGatewayConnectTimeout is the default amount of time connections to 362 // upstreams are allowed before timing out. 363 defaultGatewayConnectTimeout = 5 * time.Second 364) 365 366// ConsulGatewayProxy is used to tune parameters of the proxy instance acting as 367// one of the forms of Connect gateways that Consul supports. 368// 369// https://www.consul.io/docs/connect/proxies/envoy#gateway-options 370type ConsulGatewayProxy struct { 371 ConnectTimeout *time.Duration `mapstructure:"connect_timeout" hcl:"connect_timeout,optional"` 372 EnvoyGatewayBindTaggedAddresses bool `mapstructure:"envoy_gateway_bind_tagged_addresses" hcl:"envoy_gateway_bind_tagged_addresses,optional"` 373 EnvoyGatewayBindAddresses map[string]*ConsulGatewayBindAddress `mapstructure:"envoy_gateway_bind_addresses" hcl:"envoy_gateway_bind_addresses,block"` 374 EnvoyGatewayNoDefaultBind bool `mapstructure:"envoy_gateway_no_default_bind" hcl:"envoy_gateway_no_default_bind,optional"` 375 EnvoyDNSDiscoveryType string `mapstructure:"envoy_dns_discovery_type" hcl:"envoy_dns_discovery_type,optional"` 376 Config map[string]interface{} `hcl:"config,block"` // escape hatch envoy config 377} 378 379func (p *ConsulGatewayProxy) Canonicalize() { 380 if p == nil { 381 return 382 } 383 384 if p.ConnectTimeout == nil { 385 // same as the default from consul 386 p.ConnectTimeout = timeToPtr(defaultGatewayConnectTimeout) 387 } 388 389 if len(p.EnvoyGatewayBindAddresses) == 0 { 390 p.EnvoyGatewayBindAddresses = nil 391 } 392 393 if len(p.Config) == 0 { 394 p.Config = nil 395 } 396} 397 398func (p *ConsulGatewayProxy) Copy() *ConsulGatewayProxy { 399 if p == nil { 400 return nil 401 } 402 403 var binds map[string]*ConsulGatewayBindAddress = nil 404 if p.EnvoyGatewayBindAddresses != nil { 405 binds = make(map[string]*ConsulGatewayBindAddress, len(p.EnvoyGatewayBindAddresses)) 406 for k, v := range p.EnvoyGatewayBindAddresses { 407 binds[k] = v 408 } 409 } 410 411 var config map[string]interface{} = nil 412 if p.Config != nil { 413 config = make(map[string]interface{}, len(p.Config)) 414 for k, v := range p.Config { 415 config[k] = v 416 } 417 } 418 419 return &ConsulGatewayProxy{ 420 ConnectTimeout: timeToPtr(*p.ConnectTimeout), 421 EnvoyGatewayBindTaggedAddresses: p.EnvoyGatewayBindTaggedAddresses, 422 EnvoyGatewayBindAddresses: binds, 423 EnvoyGatewayNoDefaultBind: p.EnvoyGatewayNoDefaultBind, 424 EnvoyDNSDiscoveryType: p.EnvoyDNSDiscoveryType, 425 Config: config, 426 } 427} 428 429// ConsulGatewayTLSConfig is used to configure TLS for a gateway. 430type ConsulGatewayTLSConfig struct { 431 Enabled bool `hcl:"enabled,optional"` 432} 433 434func (tc *ConsulGatewayTLSConfig) Canonicalize() { 435} 436 437func (tc *ConsulGatewayTLSConfig) Copy() *ConsulGatewayTLSConfig { 438 if tc == nil { 439 return nil 440 } 441 442 return &ConsulGatewayTLSConfig{ 443 Enabled: tc.Enabled, 444 } 445} 446 447// ConsulIngressService is used to configure a service fronted by the ingress gateway. 448type ConsulIngressService struct { 449 // Namespace is not yet supported. 450 // Namespace string 451 Name string `hcl:"name,optional"` 452 453 Hosts []string `hcl:"hosts,optional"` 454} 455 456func (s *ConsulIngressService) Canonicalize() { 457 if s == nil { 458 return 459 } 460 461 if len(s.Hosts) == 0 { 462 s.Hosts = nil 463 } 464} 465 466func (s *ConsulIngressService) Copy() *ConsulIngressService { 467 if s == nil { 468 return nil 469 } 470 471 var hosts []string = nil 472 if n := len(s.Hosts); n > 0 { 473 hosts = make([]string, n) 474 copy(hosts, s.Hosts) 475 } 476 477 return &ConsulIngressService{ 478 Name: s.Name, 479 Hosts: hosts, 480 } 481} 482 483const ( 484 defaultIngressListenerProtocol = "tcp" 485) 486 487// ConsulIngressListener is used to configure a listener on a Consul Ingress 488// Gateway. 489type ConsulIngressListener struct { 490 Port int `hcl:"port,optional"` 491 Protocol string `hcl:"protocol,optional"` 492 Services []*ConsulIngressService `hcl:"service,block"` 493} 494 495func (l *ConsulIngressListener) Canonicalize() { 496 if l == nil { 497 return 498 } 499 500 if l.Protocol == "" { 501 // same as default from consul 502 l.Protocol = defaultIngressListenerProtocol 503 } 504 505 if len(l.Services) == 0 { 506 l.Services = nil 507 } 508} 509 510func (l *ConsulIngressListener) Copy() *ConsulIngressListener { 511 if l == nil { 512 return nil 513 } 514 515 var services []*ConsulIngressService = nil 516 if n := len(l.Services); n > 0 { 517 services = make([]*ConsulIngressService, n) 518 for i := 0; i < n; i++ { 519 services[i] = l.Services[i].Copy() 520 } 521 } 522 523 return &ConsulIngressListener{ 524 Port: l.Port, 525 Protocol: l.Protocol, 526 Services: services, 527 } 528} 529 530// ConsulIngressConfigEntry represents the Consul Configuration Entry type for 531// an Ingress Gateway. 532// 533// https://www.consul.io/docs/agent/config-entries/ingress-gateway#available-fields 534type ConsulIngressConfigEntry struct { 535 // Namespace is not yet supported. 536 // Namespace string 537 538 TLS *ConsulGatewayTLSConfig `hcl:"tls,block"` 539 Listeners []*ConsulIngressListener `hcl:"listener,block"` 540} 541 542func (e *ConsulIngressConfigEntry) Canonicalize() { 543 if e == nil { 544 return 545 } 546 547 e.TLS.Canonicalize() 548 549 if len(e.Listeners) == 0 { 550 e.Listeners = nil 551 } 552 553 for _, listener := range e.Listeners { 554 listener.Canonicalize() 555 } 556} 557 558func (e *ConsulIngressConfigEntry) Copy() *ConsulIngressConfigEntry { 559 if e == nil { 560 return nil 561 } 562 563 var listeners []*ConsulIngressListener = nil 564 if n := len(e.Listeners); n > 0 { 565 listeners = make([]*ConsulIngressListener, n) 566 for i := 0; i < n; i++ { 567 listeners[i] = e.Listeners[i].Copy() 568 } 569 } 570 571 return &ConsulIngressConfigEntry{ 572 TLS: e.TLS.Copy(), 573 Listeners: listeners, 574 } 575} 576 577type ConsulLinkedService struct { 578 Name string `hcl:"name,optional"` 579 CAFile string `hcl:"ca_file,optional"` 580 CertFile string `hcl:"cert_file,optional"` 581 KeyFile string `hcl:"key_file,optional"` 582 SNI string `hcl:"sni,optional"` 583} 584 585func (s *ConsulLinkedService) Canonicalize() { 586 // nothing to do for now 587} 588 589func (s *ConsulLinkedService) Copy() *ConsulLinkedService { 590 if s == nil { 591 return nil 592 } 593 594 return &ConsulLinkedService{ 595 Name: s.Name, 596 CAFile: s.CAFile, 597 CertFile: s.CertFile, 598 KeyFile: s.KeyFile, 599 SNI: s.SNI, 600 } 601} 602 603// ConsulTerminatingConfigEntry represents the Consul Configuration Entry type 604// for a Terminating Gateway. 605// 606// https://www.consul.io/docs/agent/config-entries/terminating-gateway#available-fields 607type ConsulTerminatingConfigEntry struct { 608 // Namespace is not yet supported. 609 // Namespace string 610 611 Services []*ConsulLinkedService `hcl:"service,block"` 612} 613 614func (e *ConsulTerminatingConfigEntry) Canonicalize() { 615 if e == nil { 616 return 617 } 618 619 if len(e.Services) == 0 { 620 e.Services = nil 621 } 622 623 for _, service := range e.Services { 624 service.Canonicalize() 625 } 626} 627 628func (e *ConsulTerminatingConfigEntry) Copy() *ConsulTerminatingConfigEntry { 629 if e == nil { 630 return nil 631 } 632 633 var services []*ConsulLinkedService = nil 634 if n := len(e.Services); n > 0 { 635 services = make([]*ConsulLinkedService, n) 636 for i := 0; i < n; i++ { 637 services[i] = e.Services[i].Copy() 638 } 639 } 640 641 return &ConsulTerminatingConfigEntry{ 642 Services: services, 643 } 644} 645 646// ConsulMeshConfigEntry is not yet supported. 647// type ConsulMeshConfigEntry struct { 648// } 649