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