github.com/manicqin/nomad@v0.9.5/nomad/structs/services.go (about)

     1  package structs
     2  
     3  import (
     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  
    21  const (
    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.
    40  type 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.
    62  func (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.
    75  func (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  
   156  func (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
   179  func (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.
   243  func (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.
   254  func (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.
   262  func (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  
   307  const (
   308  	AddressModeAuto   = "auto"
   309  	AddressModeHost   = "host"
   310  	AddressModeDriver = "driver"
   311  )
   312  
   313  // Service represents a Consul service definition
   314  type 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.
   337  func (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.
   363  func (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
   390  func (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
   439  func (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.
   453  func (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.
   483  func (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  
   500  OUTER:
   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.
   537  type 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.
   550  func (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.
   563  func (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
   576  func (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.
   581  func (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.
   599  type 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
   613  func (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.
   618  func (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.
   627  func (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
   645  type 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  
   685  func (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
   715  func (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  			if t.LogConfig.FileExtension != "" {
   774  				task.LogConfig.FileExtension = t.LogConfig.FileExtension
   775  			}
   776  		}
   777  	}
   778  
   779  	if t.ShutdownDelay != nil {
   780  		task.ShutdownDelay = *t.ShutdownDelay
   781  	}
   782  
   783  	if t.KillSignal != "" {
   784  		task.KillSignal = t.KillSignal
   785  	}
   786  }
   787  
   788  // ConsulProxy represents a Consul Connect sidecar proxy jobspec stanza.
   789  type ConsulProxy struct {
   790  
   791  	// LocalServiceAddress is the address the local service binds to.
   792  	// Usually 127.0.0.1 it is useful to customize in clusters with mixed
   793  	// Connect and non-Connect services.
   794  	LocalServiceAddress string
   795  
   796  	// LocalServicePort is the port the local service binds to. Usually
   797  	// the same as the parent service's port, it is useful to customize
   798  	// in clusters with mixed Connect and non-Connect services
   799  	LocalServicePort int
   800  
   801  	// Upstreams configures the upstream services this service intends to
   802  	// connect to.
   803  	Upstreams []ConsulUpstream
   804  
   805  	// Config is a proxy configuration. It is opaque to Nomad and passed
   806  	// directly to Consul.
   807  	Config map[string]interface{}
   808  }
   809  
   810  // Copy the stanza recursively. Returns nil if nil.
   811  func (p *ConsulProxy) Copy() *ConsulProxy {
   812  	if p == nil {
   813  		return nil
   814  	}
   815  
   816  	newP := ConsulProxy{}
   817  	newP.LocalServiceAddress = p.LocalServiceAddress
   818  	newP.LocalServicePort = p.LocalServicePort
   819  
   820  	if n := len(p.Upstreams); n > 0 {
   821  		newP.Upstreams = make([]ConsulUpstream, n)
   822  
   823  		for i := range p.Upstreams {
   824  			newP.Upstreams[i] = *p.Upstreams[i].Copy()
   825  		}
   826  	}
   827  
   828  	if n := len(p.Config); n > 0 {
   829  		newP.Config = make(map[string]interface{}, n)
   830  
   831  		for k, v := range p.Config {
   832  			newP.Config[k] = v
   833  		}
   834  	}
   835  
   836  	return &newP
   837  }
   838  
   839  // Equals returns true if the structs are recursively equal.
   840  func (p *ConsulProxy) Equals(o *ConsulProxy) bool {
   841  	if p == nil || o == nil {
   842  		return p == o
   843  	}
   844  
   845  	if p.LocalServiceAddress != o.LocalServiceAddress {
   846  		return false
   847  	}
   848  	if p.LocalServicePort != o.LocalServicePort {
   849  		return false
   850  	}
   851  	if len(p.Upstreams) != len(o.Upstreams) {
   852  		return false
   853  	}
   854  
   855  	// Order doesn't matter
   856  OUTER:
   857  	for _, up := range p.Upstreams {
   858  		for _, innerUp := range o.Upstreams {
   859  			if up.Equals(&innerUp) {
   860  				// Match; find next upstream
   861  				continue OUTER
   862  			}
   863  		}
   864  
   865  		// No match
   866  		return false
   867  	}
   868  
   869  	// Avoid nil vs {} differences
   870  	if len(p.Config) != 0 && len(o.Config) != 0 {
   871  		if !reflect.DeepEqual(p.Config, o.Config) {
   872  			return false
   873  		}
   874  	}
   875  
   876  	return true
   877  }
   878  
   879  // ConsulUpstream represents a Consul Connect upstream jobspec stanza.
   880  type ConsulUpstream struct {
   881  	// DestinationName is the name of the upstream service.
   882  	DestinationName string
   883  
   884  	// LocalBindPort is the port the proxy will receive connections for the
   885  	// upstream on.
   886  	LocalBindPort int
   887  }
   888  
   889  // Copy the stanza recursively. Returns nil if nil.
   890  func (u *ConsulUpstream) Copy() *ConsulUpstream {
   891  	if u == nil {
   892  		return nil
   893  	}
   894  
   895  	return &ConsulUpstream{
   896  		DestinationName: u.DestinationName,
   897  		LocalBindPort:   u.LocalBindPort,
   898  	}
   899  }
   900  
   901  // Equals returns true if the structs are recursively equal.
   902  func (u *ConsulUpstream) Equals(o *ConsulUpstream) bool {
   903  	if u == nil || o == nil {
   904  		return u == o
   905  	}
   906  
   907  	return (*u) == (*o)
   908  }