1package structs 2 3import ( 4 "crypto/sha1" 5 "fmt" 6 "io" 7 "net/url" 8 "reflect" 9 "regexp" 10 "sort" 11 "strings" 12 "time" 13 14 "github.com/hashicorp/consul/api" 15 multierror "github.com/hashicorp/go-multierror" 16 "github.com/hashicorp/nomad/helper" 17 "github.com/hashicorp/nomad/helper/args" 18 "github.com/mitchellh/copystructure" 19) 20 21const ( 22 EnvoyBootstrapPath = "${NOMAD_SECRETS_DIR}/envoy_bootstrap.json" 23 24 ServiceCheckHTTP = "http" 25 ServiceCheckTCP = "tcp" 26 ServiceCheckScript = "script" 27 ServiceCheckGRPC = "grpc" 28 29 // minCheckInterval is the minimum check interval permitted. Consul 30 // currently has its MinInterval set to 1s. Mirror that here for 31 // consistency. 32 minCheckInterval = 1 * time.Second 33 34 // minCheckTimeout is the minimum check timeout permitted for Consul 35 // script TTL checks. 36 minCheckTimeout = 1 * time.Second 37) 38 39// ServiceCheck represents the Consul health check. 40type ServiceCheck struct { 41 Name string // Name of the check, defaults to id 42 Type string // Type of the check - tcp, http, docker and script 43 Command string // Command is the command to run for script checks 44 Args []string // Args is a list of arguments for script checks 45 Path string // path of the health check url for http type check 46 Protocol string // Protocol to use if check is http, defaults to http 47 PortLabel string // The port to use for tcp/http checks 48 AddressMode string // 'host' to use host ip:port or 'driver' to use driver's 49 Interval time.Duration // Interval of the check 50 Timeout time.Duration // Timeout of the response from the check before consul fails the check 51 InitialStatus string // Initial status of the check 52 TLSSkipVerify bool // Skip TLS verification when Protocol=https 53 Method string // HTTP Method to use (GET by default) 54 Header map[string][]string // HTTP Headers for Consul to set when making HTTP checks 55 CheckRestart *CheckRestart // If and when a task should be restarted based on checks 56 GRPCService string // Service for GRPC checks 57 GRPCUseTLS bool // Whether or not to use TLS for GRPC checks 58 TaskName string // What task to execute this check in 59} 60 61// Copy the stanza recursively. Returns nil if nil. 62func (sc *ServiceCheck) Copy() *ServiceCheck { 63 if sc == nil { 64 return nil 65 } 66 nsc := new(ServiceCheck) 67 *nsc = *sc 68 nsc.Args = helper.CopySliceString(sc.Args) 69 nsc.Header = helper.CopyMapStringSliceString(sc.Header) 70 nsc.CheckRestart = sc.CheckRestart.Copy() 71 return nsc 72} 73 74// Equals returns true if the structs are recursively equal. 75func (sc *ServiceCheck) Equals(o *ServiceCheck) bool { 76 if sc == nil || o == nil { 77 return sc == o 78 } 79 80 if sc.Name != o.Name { 81 return false 82 } 83 84 if sc.AddressMode != o.AddressMode { 85 return false 86 } 87 88 if !helper.CompareSliceSetString(sc.Args, o.Args) { 89 return false 90 } 91 92 if !sc.CheckRestart.Equals(o.CheckRestart) { 93 return false 94 } 95 96 if sc.TaskName != o.TaskName { 97 return false 98 } 99 100 if sc.Command != o.Command { 101 return false 102 } 103 104 if sc.GRPCService != o.GRPCService { 105 return false 106 } 107 108 if sc.GRPCUseTLS != o.GRPCUseTLS { 109 return false 110 } 111 112 // Use DeepEqual here as order of slice values could matter 113 if !reflect.DeepEqual(sc.Header, o.Header) { 114 return false 115 } 116 117 if sc.InitialStatus != o.InitialStatus { 118 return false 119 } 120 121 if sc.Interval != o.Interval { 122 return false 123 } 124 125 if sc.Method != o.Method { 126 return false 127 } 128 129 if sc.Path != o.Path { 130 return false 131 } 132 133 if sc.PortLabel != o.Path { 134 return false 135 } 136 137 if sc.Protocol != o.Protocol { 138 return false 139 } 140 141 if sc.TLSSkipVerify != o.TLSSkipVerify { 142 return false 143 } 144 145 if sc.Timeout != o.Timeout { 146 return false 147 } 148 149 if sc.Type != o.Type { 150 return false 151 } 152 153 return true 154} 155 156func (sc *ServiceCheck) Canonicalize(serviceName string) { 157 // Ensure empty maps/slices are treated as null to avoid scheduling 158 // issues when using DeepEquals. 159 if len(sc.Args) == 0 { 160 sc.Args = nil 161 } 162 163 if len(sc.Header) == 0 { 164 sc.Header = nil 165 } else { 166 for k, v := range sc.Header { 167 if len(v) == 0 { 168 sc.Header[k] = nil 169 } 170 } 171 } 172 173 if sc.Name == "" { 174 sc.Name = fmt.Sprintf("service: %q check", serviceName) 175 } 176} 177 178// validate a Service's ServiceCheck 179func (sc *ServiceCheck) validate() error { 180 // Validate Type 181 switch strings.ToLower(sc.Type) { 182 case ServiceCheckGRPC: 183 case ServiceCheckTCP: 184 case ServiceCheckHTTP: 185 if sc.Path == "" { 186 return fmt.Errorf("http type must have a valid http path") 187 } 188 url, err := url.Parse(sc.Path) 189 if err != nil { 190 return fmt.Errorf("http type must have a valid http path") 191 } 192 if url.IsAbs() { 193 return fmt.Errorf("http type must have a relative http path") 194 } 195 196 case ServiceCheckScript: 197 if sc.Command == "" { 198 return fmt.Errorf("script type must have a valid script path") 199 } 200 201 default: 202 return fmt.Errorf(`invalid type (%+q), must be one of "http", "tcp", or "script" type`, sc.Type) 203 } 204 205 // Validate interval and timeout 206 if sc.Interval == 0 { 207 return fmt.Errorf("missing required value interval. Interval cannot be less than %v", minCheckInterval) 208 } else if sc.Interval < minCheckInterval { 209 return fmt.Errorf("interval (%v) cannot be lower than %v", sc.Interval, minCheckInterval) 210 } 211 212 if sc.Timeout == 0 { 213 return fmt.Errorf("missing required value timeout. Timeout cannot be less than %v", minCheckInterval) 214 } else if sc.Timeout < minCheckTimeout { 215 return fmt.Errorf("timeout (%v) is lower than required minimum timeout %v", sc.Timeout, minCheckInterval) 216 } 217 218 // Validate InitialStatus 219 switch sc.InitialStatus { 220 case "": 221 case api.HealthPassing: 222 case api.HealthWarning: 223 case api.HealthCritical: 224 default: 225 return fmt.Errorf(`invalid initial check state (%s), must be one of %q, %q, %q or empty`, sc.InitialStatus, api.HealthPassing, api.HealthWarning, api.HealthCritical) 226 227 } 228 229 // Validate AddressMode 230 switch sc.AddressMode { 231 case "", AddressModeHost, AddressModeDriver: 232 // Ok 233 case AddressModeAuto: 234 return fmt.Errorf("invalid address_mode %q - %s only valid for services", sc.AddressMode, AddressModeAuto) 235 default: 236 return fmt.Errorf("invalid address_mode %q", sc.AddressMode) 237 } 238 239 return sc.CheckRestart.Validate() 240} 241 242// RequiresPort returns whether the service check requires the task has a port. 243func (sc *ServiceCheck) RequiresPort() bool { 244 switch sc.Type { 245 case ServiceCheckGRPC, ServiceCheckHTTP, ServiceCheckTCP: 246 return true 247 default: 248 return false 249 } 250} 251 252// TriggersRestarts returns true if this check should be watched and trigger a restart 253// on failure. 254func (sc *ServiceCheck) TriggersRestarts() bool { 255 return sc.CheckRestart != nil && sc.CheckRestart.Limit > 0 256} 257 258// Hash all ServiceCheck fields and the check's corresponding service ID to 259// create an identifier. The identifier is not guaranteed to be unique as if 260// the PortLabel is blank, the Service's PortLabel will be used after Hash is 261// called. 262func (sc *ServiceCheck) Hash(serviceID string) string { 263 h := sha1.New() 264 io.WriteString(h, serviceID) 265 io.WriteString(h, sc.Name) 266 io.WriteString(h, sc.Type) 267 io.WriteString(h, sc.Command) 268 io.WriteString(h, strings.Join(sc.Args, "")) 269 io.WriteString(h, sc.Path) 270 io.WriteString(h, sc.Protocol) 271 io.WriteString(h, sc.PortLabel) 272 io.WriteString(h, sc.Interval.String()) 273 io.WriteString(h, sc.Timeout.String()) 274 io.WriteString(h, sc.Method) 275 // Only include TLSSkipVerify if set to maintain ID stability with Nomad <0.6 276 if sc.TLSSkipVerify { 277 io.WriteString(h, "true") 278 } 279 280 // Since map iteration order isn't stable we need to write k/v pairs to 281 // a slice and sort it before hashing. 282 if len(sc.Header) > 0 { 283 headers := make([]string, 0, len(sc.Header)) 284 for k, v := range sc.Header { 285 headers = append(headers, k+strings.Join(v, "")) 286 } 287 sort.Strings(headers) 288 io.WriteString(h, strings.Join(headers, "")) 289 } 290 291 // Only include AddressMode if set to maintain ID stability with Nomad <0.7.1 292 if len(sc.AddressMode) > 0 { 293 io.WriteString(h, sc.AddressMode) 294 } 295 296 // Only include GRPC if set to maintain ID stability with Nomad <0.8.4 297 if sc.GRPCService != "" { 298 io.WriteString(h, sc.GRPCService) 299 } 300 if sc.GRPCUseTLS { 301 io.WriteString(h, "true") 302 } 303 304 return fmt.Sprintf("%x", h.Sum(nil)) 305} 306 307const ( 308 AddressModeAuto = "auto" 309 AddressModeHost = "host" 310 AddressModeDriver = "driver" 311) 312 313// Service represents a Consul service definition 314type Service struct { 315 // Name of the service registered with Consul. Consul defaults the 316 // Name to ServiceID if not specified. The Name if specified is used 317 // as one of the seed values when generating a Consul ServiceID. 318 Name string 319 320 // PortLabel is either the numeric port number or the `host:port`. 321 // To specify the port number using the host's Consul Advertise 322 // address, specify an empty host in the PortLabel (e.g. `:port`). 323 PortLabel string 324 325 // AddressMode specifies whether or not to use the host ip:port for 326 // this service. 327 AddressMode string 328 329 Tags []string // List of tags for the service 330 CanaryTags []string // List of tags for the service when it is a canary 331 Checks []*ServiceCheck // List of checks associated with the service 332 Connect *ConsulConnect // Consul Connect configuration 333 Meta map[string]string // Consul service meta 334} 335 336// Copy the stanza recursively. Returns nil if nil. 337func (s *Service) Copy() *Service { 338 if s == nil { 339 return nil 340 } 341 ns := new(Service) 342 *ns = *s 343 ns.Tags = helper.CopySliceString(ns.Tags) 344 ns.CanaryTags = helper.CopySliceString(ns.CanaryTags) 345 346 if s.Checks != nil { 347 checks := make([]*ServiceCheck, len(ns.Checks)) 348 for i, c := range ns.Checks { 349 checks[i] = c.Copy() 350 } 351 ns.Checks = checks 352 } 353 354 ns.Connect = s.Connect.Copy() 355 356 ns.Meta = helper.CopyMapStringString(s.Meta) 357 358 return ns 359} 360 361// Canonicalize interpolates values of Job, Task Group and Task in the Service 362// Name. This also generates check names, service id and check ids. 363func (s *Service) Canonicalize(job string, taskGroup string, task string) { 364 // Ensure empty lists are treated as null to avoid scheduler issues when 365 // using DeepEquals 366 if len(s.Tags) == 0 { 367 s.Tags = nil 368 } 369 if len(s.CanaryTags) == 0 { 370 s.CanaryTags = nil 371 } 372 if len(s.Checks) == 0 { 373 s.Checks = nil 374 } 375 376 s.Name = args.ReplaceEnv(s.Name, map[string]string{ 377 "JOB": job, 378 "TASKGROUP": taskGroup, 379 "TASK": task, 380 "BASE": fmt.Sprintf("%s-%s-%s", job, taskGroup, task), 381 }, 382 ) 383 384 for _, check := range s.Checks { 385 check.Canonicalize(s.Name) 386 } 387} 388 389// Validate checks if the Check definition is valid 390func (s *Service) Validate() error { 391 var mErr multierror.Error 392 393 // Ensure the service name is valid per the below RFCs but make an exception 394 // for our interpolation syntax by first stripping any environment variables from the name 395 396 serviceNameStripped := args.ReplaceEnvWithPlaceHolder(s.Name, "ENV-VAR") 397 398 if err := s.ValidateName(serviceNameStripped); err != nil { 399 mErr.Errors = append(mErr.Errors, fmt.Errorf("Service name must be valid per RFC 1123 and can contain only alphanumeric characters or dashes: %q", s.Name)) 400 } 401 402 switch s.AddressMode { 403 case "", AddressModeAuto, AddressModeHost, AddressModeDriver: 404 // OK 405 default: 406 mErr.Errors = append(mErr.Errors, fmt.Errorf("Service address_mode must be %q, %q, or %q; not %q", AddressModeAuto, AddressModeHost, AddressModeDriver, s.AddressMode)) 407 } 408 409 for _, c := range s.Checks { 410 if s.PortLabel == "" && c.PortLabel == "" && c.RequiresPort() { 411 mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %s invalid: check requires a port but neither check nor service %+q have a port", c.Name, s.Name)) 412 continue 413 } 414 415 // TCP checks against a Consul Connect enabled service are not supported 416 // due to the service being bound to the loopback interface inside the 417 // network namespace 418 if c.Type == ServiceCheckTCP && s.Connect != nil && s.Connect.SidecarService != nil { 419 mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %s invalid: tcp checks are not valid for Connect enabled services", c.Name)) 420 continue 421 } 422 423 if err := c.validate(); err != nil { 424 mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %s invalid: %v", c.Name, err)) 425 } 426 } 427 428 if s.Connect != nil { 429 if err := s.Connect.Validate(); err != nil { 430 mErr.Errors = append(mErr.Errors, err) 431 } 432 } 433 434 return mErr.ErrorOrNil() 435} 436 437// ValidateName checks if the services Name is valid and should be called after 438// the name has been interpolated 439func (s *Service) ValidateName(name string) error { 440 // Ensure the service name is valid per RFC-952 §1 441 // (https://tools.ietf.org/html/rfc952), RFC-1123 §2.1 442 // (https://tools.ietf.org/html/rfc1123), and RFC-2782 443 // (https://tools.ietf.org/html/rfc2782). 444 re := regexp.MustCompile(`^(?i:[a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])$`) 445 if !re.MatchString(name) { 446 return fmt.Errorf("Service name must be valid per RFC 1123 and can contain only alphanumeric characters or dashes and must be no longer than 63 characters: %q", name) 447 } 448 return nil 449} 450 451// Hash returns a base32 encoded hash of a Service's contents excluding checks 452// as they're hashed independently. 453func (s *Service) Hash(allocID, taskName string, canary bool) string { 454 h := sha1.New() 455 io.WriteString(h, allocID) 456 io.WriteString(h, taskName) 457 io.WriteString(h, s.Name) 458 io.WriteString(h, s.PortLabel) 459 io.WriteString(h, s.AddressMode) 460 for _, tag := range s.Tags { 461 io.WriteString(h, tag) 462 } 463 for _, tag := range s.CanaryTags { 464 io.WriteString(h, tag) 465 } 466 if len(s.Meta) > 0 { 467 fmt.Fprintf(h, "%v", s.Meta) 468 } 469 470 // Vary ID on whether or not CanaryTags will be used 471 if canary { 472 h.Write([]byte("Canary")) 473 } 474 475 // Base32 is used for encoding the hash as sha1 hashes can always be 476 // encoded without padding, only 4 bytes larger than base64, and saves 477 // 8 bytes vs hex. Since these hashes are used in Consul URLs it's nice 478 // to have a reasonably compact URL-safe representation. 479 return b32.EncodeToString(h.Sum(nil)) 480} 481 482// Equals returns true if the structs are recursively equal. 483func (s *Service) Equals(o *Service) bool { 484 if s == nil || o == nil { 485 return s == o 486 } 487 488 if s.AddressMode != o.AddressMode { 489 return false 490 } 491 492 if !helper.CompareSliceSetString(s.CanaryTags, o.CanaryTags) { 493 return false 494 } 495 496 if len(s.Checks) != len(o.Checks) { 497 return false 498 } 499 500OUTER: 501 for i := range s.Checks { 502 for ii := range o.Checks { 503 if s.Checks[i].Equals(o.Checks[ii]) { 504 // Found match; continue with next check 505 continue OUTER 506 } 507 } 508 509 // No match 510 return false 511 } 512 513 if !s.Connect.Equals(o.Connect) { 514 return false 515 } 516 517 if s.Name != o.Name { 518 return false 519 } 520 521 if s.PortLabel != o.PortLabel { 522 return false 523 } 524 525 if !reflect.DeepEqual(s.Meta, o.Meta) { 526 return false 527 } 528 529 if !helper.CompareSliceSetString(s.Tags, o.Tags) { 530 return false 531 } 532 533 return true 534} 535 536// ConsulConnect represents a Consul Connect jobspec stanza. 537type ConsulConnect struct { 538 // Native is true if a service implements Connect directly and does not 539 // need a sidecar. 540 Native bool 541 542 // SidecarService is non-nil if a service requires a sidecar. 543 SidecarService *ConsulSidecarService 544 545 // SidecarTask is non-nil if sidecar overrides are set 546 SidecarTask *SidecarTask 547} 548 549// Copy the stanza recursively. Returns nil if nil. 550func (c *ConsulConnect) Copy() *ConsulConnect { 551 if c == nil { 552 return nil 553 } 554 555 return &ConsulConnect{ 556 Native: c.Native, 557 SidecarService: c.SidecarService.Copy(), 558 SidecarTask: c.SidecarTask.Copy(), 559 } 560} 561 562// Equals returns true if the structs are recursively equal. 563func (c *ConsulConnect) Equals(o *ConsulConnect) bool { 564 if c == nil || o == nil { 565 return c == o 566 } 567 568 if c.Native != o.Native { 569 return false 570 } 571 572 return c.SidecarService.Equals(o.SidecarService) 573} 574 575// HasSidecar checks if a sidecar task is needed 576func (c *ConsulConnect) HasSidecar() bool { 577 return c != nil && c.SidecarService != nil 578} 579 580// Validate that the Connect stanza has exactly one of Native or sidecar. 581func (c *ConsulConnect) Validate() error { 582 if c == nil { 583 return nil 584 } 585 586 if c.Native && c.SidecarService != nil { 587 return fmt.Errorf("Consul Connect must be native or use a sidecar service; not both") 588 } 589 590 if !c.Native && c.SidecarService == nil { 591 return fmt.Errorf("Consul Connect must be native or use a sidecar service") 592 } 593 594 return nil 595} 596 597// ConsulSidecarService represents a Consul Connect SidecarService jobspec 598// stanza. 599type ConsulSidecarService struct { 600 // Tags are optional service tags that get registered with the sidecar service 601 // in Consul. If unset, the sidecar service inherits the parent service tags. 602 Tags []string 603 604 // Port is the service's port that the sidecar will connect to. May be 605 // a port label or a literal port number. 606 Port string 607 608 // Proxy stanza defining the sidecar proxy configuration. 609 Proxy *ConsulProxy 610} 611 612// HasUpstreams checks if the sidecar service has any upstreams configured 613func (s *ConsulSidecarService) HasUpstreams() bool { 614 return s != nil && s.Proxy != nil && len(s.Proxy.Upstreams) > 0 615} 616 617// Copy the stanza recursively. Returns nil if nil. 618func (s *ConsulSidecarService) Copy() *ConsulSidecarService { 619 return &ConsulSidecarService{ 620 Tags: helper.CopySliceString(s.Tags), 621 Port: s.Port, 622 Proxy: s.Proxy.Copy(), 623 } 624} 625 626// Equals returns true if the structs are recursively equal. 627func (s *ConsulSidecarService) Equals(o *ConsulSidecarService) bool { 628 if s == nil || o == nil { 629 return s == o 630 } 631 632 if s.Port != o.Port { 633 return false 634 } 635 636 if !helper.CompareSliceSetString(s.Tags, o.Tags) { 637 return false 638 } 639 640 return s.Proxy.Equals(o.Proxy) 641} 642 643// SidecarTask represents a subset of Task fields that are able to be overridden 644// from the sidecar_task stanza 645type SidecarTask struct { 646 // Name of the task 647 Name string 648 649 // Driver is used to control which driver is used 650 Driver string 651 652 // User is used to determine which user will run the task. It defaults to 653 // the same user the Nomad client is being run as. 654 User string 655 656 // Config is provided to the driver to initialize 657 Config map[string]interface{} 658 659 // Map of environment variables to be used by the driver 660 Env map[string]string 661 662 // Resources is the resources needed by this task 663 Resources *Resources 664 665 // Meta is used to associate arbitrary metadata with this 666 // task. This is opaque to Nomad. 667 Meta map[string]string 668 669 // KillTimeout is the time between signaling a task that it will be 670 // killed and killing it. 671 KillTimeout *time.Duration 672 673 // LogConfig provides configuration for log rotation 674 LogConfig *LogConfig 675 676 // ShutdownDelay is the duration of the delay between deregistering a 677 // task from Consul and sending it a signal to shutdown. See #2441 678 ShutdownDelay *time.Duration 679 680 // KillSignal is the kill signal to use for the task. This is an optional 681 // specification and defaults to SIGINT 682 KillSignal string 683} 684 685func (t *SidecarTask) Copy() *SidecarTask { 686 if t == nil { 687 return nil 688 } 689 nt := new(SidecarTask) 690 *nt = *t 691 nt.Env = helper.CopyMapStringString(nt.Env) 692 693 nt.Resources = nt.Resources.Copy() 694 nt.LogConfig = nt.LogConfig.Copy() 695 nt.Meta = helper.CopyMapStringString(nt.Meta) 696 697 if i, err := copystructure.Copy(nt.Config); err != nil { 698 panic(err.Error()) 699 } else { 700 nt.Config = i.(map[string]interface{}) 701 } 702 703 if t.KillTimeout != nil { 704 nt.KillTimeout = helper.TimeToPtr(*t.KillTimeout) 705 } 706 707 if t.ShutdownDelay != nil { 708 nt.ShutdownDelay = helper.TimeToPtr(*t.ShutdownDelay) 709 } 710 711 return nt 712} 713 714// MergeIntoTask merges the SidecarTask fields over the given task 715func (t *SidecarTask) MergeIntoTask(task *Task) { 716 if t.Name != "" { 717 task.Name = t.Name 718 } 719 720 // If the driver changes then the driver config can be overwritten. 721 // Otherwise we'll merge the driver config together 722 if t.Driver != "" && t.Driver != task.Driver { 723 task.Driver = t.Driver 724 task.Config = t.Config 725 } else { 726 for k, v := range t.Config { 727 task.Config[k] = v 728 } 729 } 730 731 if t.User != "" { 732 task.User = t.User 733 } 734 735 if t.Env != nil { 736 if task.Env == nil { 737 task.Env = t.Env 738 } else { 739 for k, v := range t.Env { 740 task.Env[k] = v 741 } 742 } 743 } 744 745 if t.Resources != nil { 746 task.Resources.Merge(t.Resources) 747 } 748 749 if t.Meta != nil { 750 if task.Meta == nil { 751 task.Meta = t.Meta 752 } else { 753 for k, v := range t.Meta { 754 task.Meta[k] = v 755 } 756 } 757 } 758 759 if t.KillTimeout != nil { 760 task.KillTimeout = *t.KillTimeout 761 } 762 763 if t.LogConfig != nil { 764 if task.LogConfig == nil { 765 task.LogConfig = t.LogConfig 766 } else { 767 if t.LogConfig.MaxFiles > 0 { 768 task.LogConfig.MaxFiles = t.LogConfig.MaxFiles 769 } 770 if t.LogConfig.MaxFileSizeMB > 0 { 771 task.LogConfig.MaxFileSizeMB = t.LogConfig.MaxFileSizeMB 772 } 773 } 774 } 775 776 if t.ShutdownDelay != nil { 777 task.ShutdownDelay = *t.ShutdownDelay 778 } 779 780 if t.KillSignal != "" { 781 task.KillSignal = t.KillSignal 782 } 783} 784 785// ConsulProxy represents a Consul Connect sidecar proxy jobspec stanza. 786type ConsulProxy struct { 787 788 // LocalServiceAddress is the address the local service binds to. 789 // Usually 127.0.0.1 it is useful to customize in clusters with mixed 790 // Connect and non-Connect services. 791 LocalServiceAddress string 792 793 // LocalServicePort is the port the local service binds to. Usually 794 // the same as the parent service's port, it is useful to customize 795 // in clusters with mixed Connect and non-Connect services 796 LocalServicePort int 797 798 // Upstreams configures the upstream services this service intends to 799 // connect to. 800 Upstreams []ConsulUpstream 801 802 // Config is a proxy configuration. It is opaque to Nomad and passed 803 // directly to Consul. 804 Config map[string]interface{} 805} 806 807// Copy the stanza recursively. Returns nil if nil. 808func (p *ConsulProxy) Copy() *ConsulProxy { 809 if p == nil { 810 return nil 811 } 812 813 newP := ConsulProxy{} 814 newP.LocalServiceAddress = p.LocalServiceAddress 815 newP.LocalServicePort = p.LocalServicePort 816 817 if n := len(p.Upstreams); n > 0 { 818 newP.Upstreams = make([]ConsulUpstream, n) 819 820 for i := range p.Upstreams { 821 newP.Upstreams[i] = *p.Upstreams[i].Copy() 822 } 823 } 824 825 if n := len(p.Config); n > 0 { 826 newP.Config = make(map[string]interface{}, n) 827 828 for k, v := range p.Config { 829 newP.Config[k] = v 830 } 831 } 832 833 return &newP 834} 835 836// Equals returns true if the structs are recursively equal. 837func (p *ConsulProxy) Equals(o *ConsulProxy) bool { 838 if p == nil || o == nil { 839 return p == o 840 } 841 842 if p.LocalServiceAddress != o.LocalServiceAddress { 843 return false 844 } 845 if p.LocalServicePort != o.LocalServicePort { 846 return false 847 } 848 if len(p.Upstreams) != len(o.Upstreams) { 849 return false 850 } 851 852 // Order doesn't matter 853OUTER: 854 for _, up := range p.Upstreams { 855 for _, innerUp := range o.Upstreams { 856 if up.Equals(&innerUp) { 857 // Match; find next upstream 858 continue OUTER 859 } 860 } 861 862 // No match 863 return false 864 } 865 866 // Avoid nil vs {} differences 867 if len(p.Config) != 0 && len(o.Config) != 0 { 868 if !reflect.DeepEqual(p.Config, o.Config) { 869 return false 870 } 871 } 872 873 return true 874} 875 876// ConsulUpstream represents a Consul Connect upstream jobspec stanza. 877type ConsulUpstream struct { 878 // DestinationName is the name of the upstream service. 879 DestinationName string 880 881 // LocalBindPort is the port the proxy will receive connections for the 882 // upstream on. 883 LocalBindPort int 884} 885 886// Copy the stanza recursively. Returns nil if nil. 887func (u *ConsulUpstream) Copy() *ConsulUpstream { 888 if u == nil { 889 return nil 890 } 891 892 return &ConsulUpstream{ 893 DestinationName: u.DestinationName, 894 LocalBindPort: u.LocalBindPort, 895 } 896} 897 898// Equals returns true if the structs are recursively equal. 899func (u *ConsulUpstream) Equals(o *ConsulUpstream) bool { 900 if u == nil || o == nil { 901 return u == o 902 } 903 904 return (*u) == (*o) 905} 906