github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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  	SuccessBeforePassing   int                 // Number of consecutive successes required before considered healthy
    63  	FailuresBeforeCritical int                 // Number of consecutive failures required before considered unhealthy
    64  	OnUpdate               string
    65  }
    66  
    67  // Copy the stanza recursively. Returns nil if nil.
    68  func (sc *ServiceCheck) Copy() *ServiceCheck {
    69  	if sc == nil {
    70  		return nil
    71  	}
    72  	nsc := new(ServiceCheck)
    73  	*nsc = *sc
    74  	nsc.Args = helper.CopySliceString(sc.Args)
    75  	nsc.Header = helper.CopyMapStringSliceString(sc.Header)
    76  	nsc.CheckRestart = sc.CheckRestart.Copy()
    77  	return nsc
    78  }
    79  
    80  // Equals returns true if the structs are recursively equal.
    81  func (sc *ServiceCheck) Equals(o *ServiceCheck) bool {
    82  	if sc == nil || o == nil {
    83  		return sc == o
    84  	}
    85  
    86  	if sc.Name != o.Name {
    87  		return false
    88  	}
    89  
    90  	if sc.AddressMode != o.AddressMode {
    91  		return false
    92  	}
    93  
    94  	if !helper.CompareSliceSetString(sc.Args, o.Args) {
    95  		return false
    96  	}
    97  
    98  	if !sc.CheckRestart.Equals(o.CheckRestart) {
    99  		return false
   100  	}
   101  
   102  	if sc.TaskName != o.TaskName {
   103  		return false
   104  	}
   105  
   106  	if sc.SuccessBeforePassing != o.SuccessBeforePassing {
   107  		return false
   108  	}
   109  
   110  	if sc.FailuresBeforeCritical != o.FailuresBeforeCritical {
   111  		return false
   112  	}
   113  
   114  	if sc.Command != o.Command {
   115  		return false
   116  	}
   117  
   118  	if sc.GRPCService != o.GRPCService {
   119  		return false
   120  	}
   121  
   122  	if sc.GRPCUseTLS != o.GRPCUseTLS {
   123  		return false
   124  	}
   125  
   126  	// Use DeepEqual here as order of slice values could matter
   127  	if !reflect.DeepEqual(sc.Header, o.Header) {
   128  		return false
   129  	}
   130  
   131  	if sc.InitialStatus != o.InitialStatus {
   132  		return false
   133  	}
   134  
   135  	if sc.Interval != o.Interval {
   136  		return false
   137  	}
   138  
   139  	if sc.Method != o.Method {
   140  		return false
   141  	}
   142  
   143  	if sc.Path != o.Path {
   144  		return false
   145  	}
   146  
   147  	if sc.PortLabel != o.Path {
   148  		return false
   149  	}
   150  
   151  	if sc.Expose != o.Expose {
   152  		return false
   153  	}
   154  
   155  	if sc.Protocol != o.Protocol {
   156  		return false
   157  	}
   158  
   159  	if sc.TLSSkipVerify != o.TLSSkipVerify {
   160  		return false
   161  	}
   162  
   163  	if sc.Timeout != o.Timeout {
   164  		return false
   165  	}
   166  
   167  	if sc.Type != o.Type {
   168  		return false
   169  	}
   170  
   171  	if sc.OnUpdate != o.OnUpdate {
   172  		return false
   173  	}
   174  
   175  	return true
   176  }
   177  
   178  func (sc *ServiceCheck) Canonicalize(serviceName string) {
   179  	// Ensure empty maps/slices are treated as null to avoid scheduling
   180  	// issues when using DeepEquals.
   181  	if len(sc.Args) == 0 {
   182  		sc.Args = nil
   183  	}
   184  
   185  	if len(sc.Header) == 0 {
   186  		sc.Header = nil
   187  	} else {
   188  		for k, v := range sc.Header {
   189  			if len(v) == 0 {
   190  				sc.Header[k] = nil
   191  			}
   192  		}
   193  	}
   194  
   195  	if sc.Name == "" {
   196  		sc.Name = fmt.Sprintf("service: %q check", serviceName)
   197  	}
   198  
   199  	if sc.OnUpdate == "" {
   200  		sc.OnUpdate = OnUpdateRequireHealthy
   201  	}
   202  }
   203  
   204  // validate a Service's ServiceCheck
   205  func (sc *ServiceCheck) validate() error {
   206  	// Validate Type
   207  	checkType := strings.ToLower(sc.Type)
   208  	switch checkType {
   209  	case ServiceCheckGRPC:
   210  	case ServiceCheckTCP:
   211  	case ServiceCheckHTTP:
   212  		if sc.Path == "" {
   213  			return fmt.Errorf("http type must have a valid http path")
   214  		}
   215  		checkPath, err := url.Parse(sc.Path)
   216  		if err != nil {
   217  			return fmt.Errorf("http type must have a valid http path")
   218  		}
   219  		if checkPath.IsAbs() {
   220  			return fmt.Errorf("http type must have a relative http path")
   221  		}
   222  
   223  	case ServiceCheckScript:
   224  		if sc.Command == "" {
   225  			return fmt.Errorf("script type must have a valid script path")
   226  		}
   227  
   228  	default:
   229  		return fmt.Errorf(`invalid type (%+q), must be one of "http", "tcp", or "script" type`, sc.Type)
   230  	}
   231  
   232  	// Validate interval and timeout
   233  	if sc.Interval == 0 {
   234  		return fmt.Errorf("missing required value interval. Interval cannot be less than %v", minCheckInterval)
   235  	} else if sc.Interval < minCheckInterval {
   236  		return fmt.Errorf("interval (%v) cannot be lower than %v", sc.Interval, minCheckInterval)
   237  	}
   238  
   239  	if sc.Timeout == 0 {
   240  		return fmt.Errorf("missing required value timeout. Timeout cannot be less than %v", minCheckInterval)
   241  	} else if sc.Timeout < minCheckTimeout {
   242  		return fmt.Errorf("timeout (%v) is lower than required minimum timeout %v", sc.Timeout, minCheckInterval)
   243  	}
   244  
   245  	// Validate InitialStatus
   246  	switch sc.InitialStatus {
   247  	case "":
   248  	case api.HealthPassing:
   249  	case api.HealthWarning:
   250  	case api.HealthCritical:
   251  	default:
   252  		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)
   253  
   254  	}
   255  
   256  	// Validate AddressMode
   257  	switch sc.AddressMode {
   258  	case "", AddressModeHost, AddressModeDriver, AddressModeAlloc:
   259  		// Ok
   260  	case AddressModeAuto:
   261  		return fmt.Errorf("invalid address_mode %q - %s only valid for services", sc.AddressMode, AddressModeAuto)
   262  	default:
   263  		return fmt.Errorf("invalid address_mode %q", sc.AddressMode)
   264  	}
   265  
   266  	// Validate OnUpdate
   267  	switch sc.OnUpdate {
   268  	case "", OnUpdateIgnore, OnUpdateRequireHealthy, OnUpdateIgnoreWarn:
   269  		// OK
   270  	default:
   271  		return fmt.Errorf("on_update must be %q, %q, or %q; got %q", OnUpdateRequireHealthy, OnUpdateIgnoreWarn, OnUpdateIgnore, sc.OnUpdate)
   272  	}
   273  
   274  	// Note that we cannot completely validate the Expose field yet - we do not
   275  	// know whether this ServiceCheck belongs to a connect-enabled group-service.
   276  	// Instead, such validation will happen in a job admission controller.
   277  	if sc.Expose {
   278  		// We can however immediately ensure expose is configured only for HTTP
   279  		// and gRPC checks.
   280  		switch checkType {
   281  		case ServiceCheckGRPC, ServiceCheckHTTP: // ok
   282  		default:
   283  			return fmt.Errorf("expose may only be set on HTTP or gRPC checks")
   284  		}
   285  	}
   286  
   287  	// passFailCheckTypes are intersection of check types supported by both Consul
   288  	// and Nomad when using the pass/fail check threshold features.
   289  	passFailCheckTypes := []string{"tcp", "http", "grpc"}
   290  
   291  	if sc.SuccessBeforePassing < 0 {
   292  		return fmt.Errorf("success_before_passing must be non-negative")
   293  	} else if sc.SuccessBeforePassing > 0 && !helper.SliceStringContains(passFailCheckTypes, sc.Type) {
   294  		return fmt.Errorf("success_before_passing not supported for check of type %q", sc.Type)
   295  	}
   296  
   297  	if sc.FailuresBeforeCritical < 0 {
   298  		return fmt.Errorf("failures_before_critical must be non-negative")
   299  	} else if sc.FailuresBeforeCritical > 0 && !helper.SliceStringContains(passFailCheckTypes, sc.Type) {
   300  		return fmt.Errorf("failures_before_critical not supported for check of type %q", sc.Type)
   301  	}
   302  
   303  	// Check that CheckRestart and OnUpdate do not conflict
   304  	if sc.CheckRestart != nil {
   305  		// CheckRestart and OnUpdate Ignore are incompatible If OnUpdate treats
   306  		// an error has healthy, and the deployment succeeds followed by check
   307  		// restart restarting erroring checks, the deployment is left in an odd
   308  		// state
   309  		if sc.OnUpdate == OnUpdateIgnore {
   310  			return fmt.Errorf("on_update value %q is not compatible with check_restart", sc.OnUpdate)
   311  		}
   312  		// CheckRestart IgnoreWarnings must be true if a check has defined OnUpdate
   313  		// ignore_warnings
   314  		if !sc.CheckRestart.IgnoreWarnings && sc.OnUpdate == OnUpdateIgnoreWarn {
   315  			return fmt.Errorf("on_update value %q not supported with check_restart ignore_warnings value %q", sc.OnUpdate, strconv.FormatBool(sc.CheckRestart.IgnoreWarnings))
   316  		}
   317  	}
   318  
   319  	return sc.CheckRestart.Validate()
   320  }
   321  
   322  // RequiresPort returns whether the service check requires the task has a port.
   323  func (sc *ServiceCheck) RequiresPort() bool {
   324  	switch sc.Type {
   325  	case ServiceCheckGRPC, ServiceCheckHTTP, ServiceCheckTCP:
   326  		return true
   327  	default:
   328  		return false
   329  	}
   330  }
   331  
   332  // TriggersRestarts returns true if this check should be watched and trigger a restart
   333  // on failure.
   334  func (sc *ServiceCheck) TriggersRestarts() bool {
   335  	return sc.CheckRestart != nil && sc.CheckRestart.Limit > 0
   336  }
   337  
   338  // Hash all ServiceCheck fields and the check's corresponding service ID to
   339  // create an identifier. The identifier is not guaranteed to be unique as if
   340  // the PortLabel is blank, the Service's PortLabel will be used after Hash is
   341  // called.
   342  func (sc *ServiceCheck) Hash(serviceID string) string {
   343  	h := sha1.New()
   344  	hashString(h, serviceID)
   345  	hashString(h, sc.Name)
   346  	hashString(h, sc.Type)
   347  	hashString(h, sc.Command)
   348  	hashString(h, strings.Join(sc.Args, ""))
   349  	hashString(h, sc.Path)
   350  	hashString(h, sc.Protocol)
   351  	hashString(h, sc.PortLabel)
   352  	hashString(h, sc.Interval.String())
   353  	hashString(h, sc.Timeout.String())
   354  	hashString(h, sc.Method)
   355  	hashString(h, sc.OnUpdate)
   356  
   357  	// use name "true" to maintain ID stability
   358  	hashBool(h, sc.TLSSkipVerify, "true")
   359  
   360  	// maintain artisanal map hashing to maintain ID stability
   361  	hashHeader(h, sc.Header)
   362  
   363  	// Only include AddressMode if set to maintain ID stability with Nomad <0.7.1
   364  	hashStringIfNonEmpty(h, sc.AddressMode)
   365  
   366  	// Only include gRPC if set to maintain ID stability with Nomad <0.8.4
   367  	hashStringIfNonEmpty(h, sc.GRPCService)
   368  
   369  	// use name "true" to maintain ID stability
   370  	hashBool(h, sc.GRPCUseTLS, "true")
   371  
   372  	// Only include pass/fail if non-zero to maintain ID stability with Nomad < 0.12
   373  	hashIntIfNonZero(h, "success", sc.SuccessBeforePassing)
   374  	hashIntIfNonZero(h, "failures", sc.FailuresBeforeCritical)
   375  
   376  	// Hash is used for diffing against the Consul check definition, which does
   377  	// not have an expose parameter. Instead we rely on implied changes to
   378  	// other fields if the Expose setting is changed in a nomad service.
   379  	// hashBool(h, sc.Expose, "Expose")
   380  
   381  	// maintain use of hex (i.e. not b32) to maintain ID stability
   382  	return fmt.Sprintf("%x", h.Sum(nil))
   383  }
   384  
   385  func hashStringIfNonEmpty(h hash.Hash, s string) {
   386  	if len(s) > 0 {
   387  		hashString(h, s)
   388  	}
   389  }
   390  
   391  func hashIntIfNonZero(h hash.Hash, name string, i int) {
   392  	if i != 0 {
   393  		hashString(h, fmt.Sprintf("%s:%d", name, i))
   394  	}
   395  }
   396  
   397  func hashHeader(h hash.Hash, m map[string][]string) {
   398  	// maintain backwards compatibility for ID stability
   399  	// using the %v formatter on a map with string keys produces consistent
   400  	// output, but our existing format here is incompatible
   401  	if len(m) > 0 {
   402  		headers := make([]string, 0, len(m))
   403  		for k, v := range m {
   404  			headers = append(headers, k+strings.Join(v, ""))
   405  		}
   406  		sort.Strings(headers)
   407  		hashString(h, strings.Join(headers, ""))
   408  	}
   409  }
   410  
   411  const (
   412  	AddressModeAuto   = "auto"
   413  	AddressModeHost   = "host"
   414  	AddressModeDriver = "driver"
   415  	AddressModeAlloc  = "alloc"
   416  )
   417  
   418  // Service represents a Consul service definition
   419  type Service struct {
   420  	// Name of the service registered with Consul. Consul defaults the
   421  	// Name to ServiceID if not specified.  The Name if specified is used
   422  	// as one of the seed values when generating a Consul ServiceID.
   423  	Name string
   424  
   425  	// Name of the Task associated with this service.
   426  	//
   427  	// Currently only used to identify the implementing task of a Consul
   428  	// Connect Native enabled service.
   429  	TaskName string
   430  
   431  	// PortLabel is either the numeric port number or the `host:port`.
   432  	// To specify the port number using the host's Consul Advertise
   433  	// address, specify an empty host in the PortLabel (e.g. `:port`).
   434  	PortLabel string
   435  
   436  	// AddressMode specifies whether or not to use the host ip:port for
   437  	// this service.
   438  	AddressMode string
   439  
   440  	// EnableTagOverride will disable Consul's anti-entropy mechanism for the
   441  	// tags of this service. External updates to the service definition via
   442  	// Consul will not be corrected to match the service definition set in the
   443  	// Nomad job specification.
   444  	//
   445  	// https://www.consul.io/docs/agent/services.html#service-definition
   446  	EnableTagOverride bool
   447  
   448  	Tags       []string          // List of tags for the service
   449  	CanaryTags []string          // List of tags for the service when it is a canary
   450  	Checks     []*ServiceCheck   // List of checks associated with the service
   451  	Connect    *ConsulConnect    // Consul Connect configuration
   452  	Meta       map[string]string // Consul service meta
   453  	CanaryMeta map[string]string // Consul service meta when it is a canary
   454  
   455  	// OnUpdate Specifies how the service and its checks should be evaluated
   456  	// during an update
   457  	OnUpdate string
   458  }
   459  
   460  const (
   461  	OnUpdateRequireHealthy = "require_healthy"
   462  	OnUpdateIgnoreWarn     = "ignore_warnings"
   463  	OnUpdateIgnore         = "ignore"
   464  )
   465  
   466  // Copy the stanza recursively. Returns nil if nil.
   467  func (s *Service) Copy() *Service {
   468  	if s == nil {
   469  		return nil
   470  	}
   471  	ns := new(Service)
   472  	*ns = *s
   473  	ns.Tags = helper.CopySliceString(ns.Tags)
   474  	ns.CanaryTags = helper.CopySliceString(ns.CanaryTags)
   475  
   476  	if s.Checks != nil {
   477  		checks := make([]*ServiceCheck, len(ns.Checks))
   478  		for i, c := range ns.Checks {
   479  			checks[i] = c.Copy()
   480  		}
   481  		ns.Checks = checks
   482  	}
   483  
   484  	ns.Connect = s.Connect.Copy()
   485  
   486  	ns.Meta = helper.CopyMapStringString(s.Meta)
   487  	ns.CanaryMeta = helper.CopyMapStringString(s.CanaryMeta)
   488  
   489  	return ns
   490  }
   491  
   492  // Canonicalize interpolates values of Job, Task Group and Task in the Service
   493  // Name. This also generates check names, service id and check ids.
   494  func (s *Service) Canonicalize(job string, taskGroup string, task string) {
   495  	// Ensure empty lists are treated as null to avoid scheduler issues when
   496  	// using DeepEquals
   497  	if len(s.Tags) == 0 {
   498  		s.Tags = nil
   499  	}
   500  	if len(s.CanaryTags) == 0 {
   501  		s.CanaryTags = nil
   502  	}
   503  	if len(s.Checks) == 0 {
   504  		s.Checks = nil
   505  	}
   506  
   507  	s.Name = args.ReplaceEnv(s.Name, map[string]string{
   508  		"JOB":       job,
   509  		"TASKGROUP": taskGroup,
   510  		"TASK":      task,
   511  		"BASE":      fmt.Sprintf("%s-%s-%s", job, taskGroup, task),
   512  	})
   513  
   514  	for _, check := range s.Checks {
   515  		check.Canonicalize(s.Name)
   516  	}
   517  }
   518  
   519  // Validate checks if the Service definition is valid
   520  func (s *Service) Validate() error {
   521  	var mErr multierror.Error
   522  
   523  	// Ensure the service name is valid per the below RFCs but make an exception
   524  	// for our interpolation syntax by first stripping any environment variables from the name
   525  
   526  	serviceNameStripped := args.ReplaceEnvWithPlaceHolder(s.Name, "ENV-VAR")
   527  
   528  	if err := s.ValidateName(serviceNameStripped); err != nil {
   529  		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))
   530  	}
   531  
   532  	switch s.AddressMode {
   533  	case "", AddressModeAuto, AddressModeHost, AddressModeDriver, AddressModeAlloc:
   534  		// OK
   535  	default:
   536  		mErr.Errors = append(mErr.Errors, fmt.Errorf("Service address_mode must be %q, %q, or %q; not %q", AddressModeAuto, AddressModeHost, AddressModeDriver, s.AddressMode))
   537  	}
   538  
   539  	switch s.OnUpdate {
   540  	case "", OnUpdateIgnore, OnUpdateRequireHealthy, OnUpdateIgnoreWarn:
   541  		// OK
   542  	default:
   543  		mErr.Errors = append(mErr.Errors, fmt.Errorf("Service on_update must be %q, %q, or %q; not %q", OnUpdateRequireHealthy, OnUpdateIgnoreWarn, OnUpdateIgnore, s.OnUpdate))
   544  	}
   545  
   546  	// check checks
   547  	for _, c := range s.Checks {
   548  		if s.PortLabel == "" && c.PortLabel == "" && c.RequiresPort() {
   549  			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))
   550  			continue
   551  		}
   552  
   553  		// TCP checks against a Consul Connect enabled service are not supported
   554  		// due to the service being bound to the loopback interface inside the
   555  		// network namespace
   556  		if c.Type == ServiceCheckTCP && s.Connect != nil && s.Connect.SidecarService != nil {
   557  			mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %s invalid: tcp checks are not valid for Connect enabled services", c.Name))
   558  			continue
   559  		}
   560  
   561  		if err := c.validate(); err != nil {
   562  			mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %s invalid: %v", c.Name, err))
   563  		}
   564  	}
   565  
   566  	// check connect
   567  	if s.Connect != nil {
   568  		if err := s.Connect.Validate(); err != nil {
   569  			mErr.Errors = append(mErr.Errors, err)
   570  		}
   571  
   572  		// if service is connect native, service task must be set (which may
   573  		// happen implicitly in a job mutation if there is only one task)
   574  		if s.Connect.IsNative() && len(s.TaskName) == 0 {
   575  			mErr.Errors = append(mErr.Errors, fmt.Errorf("Service %s is Connect Native and requires setting the task", s.Name))
   576  		}
   577  	}
   578  
   579  	return mErr.ErrorOrNil()
   580  }
   581  
   582  // ValidateName checks if the service Name is valid and should be called after
   583  // the name has been interpolated
   584  func (s *Service) ValidateName(name string) error {
   585  	// Ensure the service name is valid per RFC-952 §1
   586  	// (https://tools.ietf.org/html/rfc952), RFC-1123 §2.1
   587  	// (https://tools.ietf.org/html/rfc1123), and RFC-2782
   588  	// (https://tools.ietf.org/html/rfc2782).
   589  	re := regexp.MustCompile(`^(?i:[a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])$`)
   590  	if !re.MatchString(name) {
   591  		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)
   592  	}
   593  	return nil
   594  }
   595  
   596  // Hash returns a base32 encoded hash of a Service's contents excluding checks
   597  // as they're hashed independently.
   598  func (s *Service) Hash(allocID, taskName string, canary bool) string {
   599  	h := sha1.New()
   600  	hashString(h, allocID)
   601  	hashString(h, taskName)
   602  	hashString(h, s.Name)
   603  	hashString(h, s.PortLabel)
   604  	hashString(h, s.AddressMode)
   605  	hashTags(h, s.Tags)
   606  	hashTags(h, s.CanaryTags)
   607  	hashBool(h, canary, "Canary")
   608  	hashBool(h, s.EnableTagOverride, "ETO")
   609  	hashMeta(h, s.Meta)
   610  	hashMeta(h, s.CanaryMeta)
   611  	hashConnect(h, s.Connect)
   612  	hashString(h, s.OnUpdate)
   613  
   614  	// Base32 is used for encoding the hash as sha1 hashes can always be
   615  	// encoded without padding, only 4 bytes larger than base64, and saves
   616  	// 8 bytes vs hex. Since these hashes are used in Consul URLs it's nice
   617  	// to have a reasonably compact URL-safe representation.
   618  	return b32.EncodeToString(h.Sum(nil))
   619  }
   620  
   621  func hashConnect(h hash.Hash, connect *ConsulConnect) {
   622  	if connect != nil && connect.SidecarService != nil {
   623  		hashString(h, connect.SidecarService.Port)
   624  		hashTags(h, connect.SidecarService.Tags)
   625  		if p := connect.SidecarService.Proxy; p != nil {
   626  			hashString(h, p.LocalServiceAddress)
   627  			hashString(h, strconv.Itoa(p.LocalServicePort))
   628  			hashConfig(h, p.Config)
   629  			for _, upstream := range p.Upstreams {
   630  				hashString(h, upstream.DestinationName)
   631  				hashString(h, strconv.Itoa(upstream.LocalBindPort))
   632  				hashStringIfNonEmpty(h, upstream.Datacenter)
   633  				hashStringIfNonEmpty(h, upstream.LocalBindAddress)
   634  			}
   635  		}
   636  	}
   637  }
   638  
   639  func hashString(h hash.Hash, s string) {
   640  	_, _ = io.WriteString(h, s)
   641  }
   642  
   643  func hashBool(h hash.Hash, b bool, name string) {
   644  	if b {
   645  		hashString(h, name)
   646  	}
   647  }
   648  
   649  func hashTags(h hash.Hash, tags []string) {
   650  	for _, tag := range tags {
   651  		hashString(h, tag)
   652  	}
   653  }
   654  
   655  func hashMeta(h hash.Hash, m map[string]string) {
   656  	_, _ = fmt.Fprintf(h, "%v", m)
   657  }
   658  
   659  func hashConfig(h hash.Hash, c map[string]interface{}) {
   660  	_, _ = fmt.Fprintf(h, "%v", c)
   661  }
   662  
   663  // Equals returns true if the structs are recursively equal.
   664  func (s *Service) Equals(o *Service) bool {
   665  	if s == nil || o == nil {
   666  		return s == o
   667  	}
   668  
   669  	if s.AddressMode != o.AddressMode {
   670  		return false
   671  	}
   672  
   673  	if s.OnUpdate != o.OnUpdate {
   674  		return false
   675  	}
   676  
   677  	if !helper.CompareSliceSetString(s.CanaryTags, o.CanaryTags) {
   678  		return false
   679  	}
   680  
   681  	if len(s.Checks) != len(o.Checks) {
   682  		return false
   683  	}
   684  
   685  OUTER:
   686  	for i := range s.Checks {
   687  		for ii := range o.Checks {
   688  			if s.Checks[i].Equals(o.Checks[ii]) {
   689  				// Found match; continue with next check
   690  				continue OUTER
   691  			}
   692  		}
   693  
   694  		// No match
   695  		return false
   696  	}
   697  
   698  	if !s.Connect.Equals(o.Connect) {
   699  		return false
   700  	}
   701  
   702  	if s.Name != o.Name {
   703  		return false
   704  	}
   705  
   706  	if s.PortLabel != o.PortLabel {
   707  		return false
   708  	}
   709  
   710  	if !reflect.DeepEqual(s.Meta, o.Meta) {
   711  		return false
   712  	}
   713  
   714  	if !reflect.DeepEqual(s.CanaryMeta, o.CanaryMeta) {
   715  		return false
   716  	}
   717  
   718  	if !helper.CompareSliceSetString(s.Tags, o.Tags) {
   719  		return false
   720  	}
   721  
   722  	if s.EnableTagOverride != o.EnableTagOverride {
   723  		return false
   724  	}
   725  
   726  	return true
   727  }
   728  
   729  // ConsulConnect represents a Consul Connect jobspec stanza.
   730  type ConsulConnect struct {
   731  	// Native indicates whether the service is Consul Connect Native enabled.
   732  	Native bool
   733  
   734  	// SidecarService is non-nil if a service requires a sidecar.
   735  	SidecarService *ConsulSidecarService
   736  
   737  	// SidecarTask is non-nil if sidecar overrides are set
   738  	SidecarTask *SidecarTask
   739  
   740  	// Gateway is a Consul Connect Gateway Proxy.
   741  	Gateway *ConsulGateway
   742  }
   743  
   744  // Copy the stanza recursively. Returns nil if nil.
   745  func (c *ConsulConnect) Copy() *ConsulConnect {
   746  	if c == nil {
   747  		return nil
   748  	}
   749  
   750  	return &ConsulConnect{
   751  		Native:         c.Native,
   752  		SidecarService: c.SidecarService.Copy(),
   753  		SidecarTask:    c.SidecarTask.Copy(),
   754  		Gateway:        c.Gateway.Copy(),
   755  	}
   756  }
   757  
   758  // Equals returns true if the connect blocks are deeply equal.
   759  func (c *ConsulConnect) Equals(o *ConsulConnect) bool {
   760  	if c == nil || o == nil {
   761  		return c == o
   762  	}
   763  
   764  	if c.Native != o.Native {
   765  		return false
   766  	}
   767  
   768  	if !c.SidecarService.Equals(o.SidecarService) {
   769  		return false
   770  	}
   771  
   772  	if !c.SidecarTask.Equals(o.SidecarTask) {
   773  		return false
   774  	}
   775  
   776  	if !c.Gateway.Equals(o.Gateway) {
   777  		return false
   778  	}
   779  
   780  	return true
   781  }
   782  
   783  // HasSidecar checks if a sidecar task is configured.
   784  func (c *ConsulConnect) HasSidecar() bool {
   785  	return c != nil && c.SidecarService != nil
   786  }
   787  
   788  // IsNative checks if the service is connect native.
   789  func (c *ConsulConnect) IsNative() bool {
   790  	return c != nil && c.Native
   791  }
   792  
   793  // IsGateway checks if the service is any type of connect gateway.
   794  func (c *ConsulConnect) IsGateway() bool {
   795  	return c != nil && c.Gateway != nil
   796  }
   797  
   798  // IsIngress checks if the service is an ingress gateway.
   799  func (c *ConsulConnect) IsIngress() bool {
   800  	return c.IsGateway() && c.Gateway.Ingress != nil
   801  }
   802  
   803  // IsTerminating checks if the service is a terminating gateway.
   804  func (c *ConsulConnect) IsTerminating() bool {
   805  	return c.IsGateway() && c.Gateway.Terminating != nil
   806  }
   807  
   808  // also mesh
   809  
   810  // Validate that the Connect block represents exactly one of:
   811  // - Connect non-native service sidecar proxy
   812  // - Connect native service
   813  // - Connect gateway (any type)
   814  func (c *ConsulConnect) Validate() error {
   815  	if c == nil {
   816  		return nil
   817  	}
   818  
   819  	// Count the number of things actually configured. If that number is not 1,
   820  	// the config is not valid.
   821  	count := 0
   822  
   823  	if c.HasSidecar() {
   824  		count++
   825  	}
   826  
   827  	if c.IsNative() {
   828  		count++
   829  	}
   830  
   831  	if c.IsGateway() {
   832  		count++
   833  	}
   834  
   835  	if count != 1 {
   836  		return fmt.Errorf("Consul Connect must be exclusively native, make use of a sidecar, or represent a Gateway")
   837  	}
   838  
   839  	if c.IsGateway() {
   840  		if err := c.Gateway.Validate(); err != nil {
   841  			return err
   842  		}
   843  	}
   844  
   845  	// The Native and Sidecar cases are validated up at the service level.
   846  
   847  	return nil
   848  }
   849  
   850  // ConsulSidecarService represents a Consul Connect SidecarService jobspec
   851  // stanza.
   852  type ConsulSidecarService struct {
   853  	// Tags are optional service tags that get registered with the sidecar service
   854  	// in Consul. If unset, the sidecar service inherits the parent service tags.
   855  	Tags []string
   856  
   857  	// Port is the service's port that the sidecar will connect to. May be
   858  	// a port label or a literal port number.
   859  	Port string
   860  
   861  	// Proxy stanza defining the sidecar proxy configuration.
   862  	Proxy *ConsulProxy
   863  }
   864  
   865  // HasUpstreams checks if the sidecar service has any upstreams configured
   866  func (s *ConsulSidecarService) HasUpstreams() bool {
   867  	return s != nil && s.Proxy != nil && len(s.Proxy.Upstreams) > 0
   868  }
   869  
   870  // Copy the stanza recursively. Returns nil if nil.
   871  func (s *ConsulSidecarService) Copy() *ConsulSidecarService {
   872  	if s == nil {
   873  		return nil
   874  	}
   875  	return &ConsulSidecarService{
   876  		Tags:  helper.CopySliceString(s.Tags),
   877  		Port:  s.Port,
   878  		Proxy: s.Proxy.Copy(),
   879  	}
   880  }
   881  
   882  // Equals returns true if the structs are recursively equal.
   883  func (s *ConsulSidecarService) Equals(o *ConsulSidecarService) bool {
   884  	if s == nil || o == nil {
   885  		return s == o
   886  	}
   887  
   888  	if s.Port != o.Port {
   889  		return false
   890  	}
   891  
   892  	if !helper.CompareSliceSetString(s.Tags, o.Tags) {
   893  		return false
   894  	}
   895  
   896  	return s.Proxy.Equals(o.Proxy)
   897  }
   898  
   899  // SidecarTask represents a subset of Task fields that are able to be overridden
   900  // from the sidecar_task stanza
   901  type SidecarTask struct {
   902  	// Name of the task
   903  	Name string
   904  
   905  	// Driver is used to control which driver is used
   906  	Driver string
   907  
   908  	// User is used to determine which user will run the task. It defaults to
   909  	// the same user the Nomad client is being run as.
   910  	User string
   911  
   912  	// Config is provided to the driver to initialize
   913  	Config map[string]interface{}
   914  
   915  	// Map of environment variables to be used by the driver
   916  	Env map[string]string
   917  
   918  	// Resources is the resources needed by this task
   919  	Resources *Resources
   920  
   921  	// Meta is used to associate arbitrary metadata with this
   922  	// task. This is opaque to Nomad.
   923  	Meta map[string]string
   924  
   925  	// KillTimeout is the time between signaling a task that it will be
   926  	// killed and killing it.
   927  	KillTimeout *time.Duration
   928  
   929  	// LogConfig provides configuration for log rotation
   930  	LogConfig *LogConfig
   931  
   932  	// ShutdownDelay is the duration of the delay between deregistering a
   933  	// task from Consul and sending it a signal to shutdown. See #2441
   934  	ShutdownDelay *time.Duration
   935  
   936  	// KillSignal is the kill signal to use for the task. This is an optional
   937  	// specification and defaults to SIGINT
   938  	KillSignal string
   939  }
   940  
   941  func (t *SidecarTask) Equals(o *SidecarTask) bool {
   942  	if t == nil || o == nil {
   943  		return t == o
   944  	}
   945  
   946  	if t.Name != o.Name {
   947  		return false
   948  	}
   949  
   950  	if t.Driver != o.Driver {
   951  		return false
   952  	}
   953  
   954  	if t.User != o.User {
   955  		return false
   956  	}
   957  
   958  	// config compare
   959  	if !opaqueMapsEqual(t.Config, o.Config) {
   960  		return false
   961  	}
   962  
   963  	if !helper.CompareMapStringString(t.Env, o.Env) {
   964  		return false
   965  	}
   966  
   967  	if !t.Resources.Equals(o.Resources) {
   968  		return false
   969  	}
   970  
   971  	if !helper.CompareMapStringString(t.Meta, o.Meta) {
   972  		return false
   973  	}
   974  
   975  	if !helper.CompareTimePtrs(t.KillTimeout, o.KillTimeout) {
   976  		return false
   977  	}
   978  
   979  	if !t.LogConfig.Equals(o.LogConfig) {
   980  		return false
   981  	}
   982  
   983  	if !helper.CompareTimePtrs(t.ShutdownDelay, o.ShutdownDelay) {
   984  		return false
   985  	}
   986  
   987  	if t.KillSignal != o.KillSignal {
   988  		return false
   989  	}
   990  
   991  	return true
   992  }
   993  
   994  func (t *SidecarTask) Copy() *SidecarTask {
   995  	if t == nil {
   996  		return nil
   997  	}
   998  	nt := new(SidecarTask)
   999  	*nt = *t
  1000  	nt.Env = helper.CopyMapStringString(nt.Env)
  1001  
  1002  	nt.Resources = nt.Resources.Copy()
  1003  	nt.LogConfig = nt.LogConfig.Copy()
  1004  	nt.Meta = helper.CopyMapStringString(nt.Meta)
  1005  
  1006  	if i, err := copystructure.Copy(nt.Config); err != nil {
  1007  		panic(err.Error())
  1008  	} else {
  1009  		nt.Config = i.(map[string]interface{})
  1010  	}
  1011  
  1012  	if t.KillTimeout != nil {
  1013  		nt.KillTimeout = helper.TimeToPtr(*t.KillTimeout)
  1014  	}
  1015  
  1016  	if t.ShutdownDelay != nil {
  1017  		nt.ShutdownDelay = helper.TimeToPtr(*t.ShutdownDelay)
  1018  	}
  1019  
  1020  	return nt
  1021  }
  1022  
  1023  // MergeIntoTask merges the SidecarTask fields over the given task
  1024  func (t *SidecarTask) MergeIntoTask(task *Task) {
  1025  	if t.Name != "" {
  1026  		task.Name = t.Name
  1027  	}
  1028  
  1029  	// If the driver changes then the driver config can be overwritten.
  1030  	// Otherwise we'll merge the driver config together
  1031  	if t.Driver != "" && t.Driver != task.Driver {
  1032  		task.Driver = t.Driver
  1033  		task.Config = t.Config
  1034  	} else {
  1035  		for k, v := range t.Config {
  1036  			task.Config[k] = v
  1037  		}
  1038  	}
  1039  
  1040  	if t.User != "" {
  1041  		task.User = t.User
  1042  	}
  1043  
  1044  	if t.Env != nil {
  1045  		if task.Env == nil {
  1046  			task.Env = t.Env
  1047  		} else {
  1048  			for k, v := range t.Env {
  1049  				task.Env[k] = v
  1050  			}
  1051  		}
  1052  	}
  1053  
  1054  	if t.Resources != nil {
  1055  		task.Resources.Merge(t.Resources)
  1056  	}
  1057  
  1058  	if t.Meta != nil {
  1059  		if task.Meta == nil {
  1060  			task.Meta = t.Meta
  1061  		} else {
  1062  			for k, v := range t.Meta {
  1063  				task.Meta[k] = v
  1064  			}
  1065  		}
  1066  	}
  1067  
  1068  	if t.KillTimeout != nil {
  1069  		task.KillTimeout = *t.KillTimeout
  1070  	}
  1071  
  1072  	if t.LogConfig != nil {
  1073  		if task.LogConfig == nil {
  1074  			task.LogConfig = t.LogConfig
  1075  		} else {
  1076  			if t.LogConfig.MaxFiles > 0 {
  1077  				task.LogConfig.MaxFiles = t.LogConfig.MaxFiles
  1078  			}
  1079  			if t.LogConfig.MaxFileSizeMB > 0 {
  1080  				task.LogConfig.MaxFileSizeMB = t.LogConfig.MaxFileSizeMB
  1081  			}
  1082  		}
  1083  	}
  1084  
  1085  	if t.ShutdownDelay != nil {
  1086  		task.ShutdownDelay = *t.ShutdownDelay
  1087  	}
  1088  
  1089  	if t.KillSignal != "" {
  1090  		task.KillSignal = t.KillSignal
  1091  	}
  1092  }
  1093  
  1094  // ConsulProxy represents a Consul Connect sidecar proxy jobspec stanza.
  1095  type ConsulProxy struct {
  1096  
  1097  	// LocalServiceAddress is the address the local service binds to.
  1098  	// Usually 127.0.0.1 it is useful to customize in clusters with mixed
  1099  	// Connect and non-Connect services.
  1100  	LocalServiceAddress string
  1101  
  1102  	// LocalServicePort is the port the local service binds to. Usually
  1103  	// the same as the parent service's port, it is useful to customize
  1104  	// in clusters with mixed Connect and non-Connect services
  1105  	LocalServicePort int
  1106  
  1107  	// Upstreams configures the upstream services this service intends to
  1108  	// connect to.
  1109  	Upstreams []ConsulUpstream
  1110  
  1111  	// Expose configures the consul proxy.expose stanza to "open up" endpoints
  1112  	// used by task-group level service checks using HTTP or gRPC protocols.
  1113  	//
  1114  	// Use json tag to match with field name in api/
  1115  	Expose *ConsulExposeConfig `json:"ExposeConfig"`
  1116  
  1117  	// Config is a proxy configuration. It is opaque to Nomad and passed
  1118  	// directly to Consul.
  1119  	Config map[string]interface{}
  1120  }
  1121  
  1122  // Copy the stanza recursively. Returns nil if nil.
  1123  func (p *ConsulProxy) Copy() *ConsulProxy {
  1124  	if p == nil {
  1125  		return nil
  1126  	}
  1127  
  1128  	newP := &ConsulProxy{
  1129  		LocalServiceAddress: p.LocalServiceAddress,
  1130  		LocalServicePort:    p.LocalServicePort,
  1131  		Expose:              p.Expose.Copy(),
  1132  	}
  1133  
  1134  	if n := len(p.Upstreams); n > 0 {
  1135  		newP.Upstreams = make([]ConsulUpstream, n)
  1136  
  1137  		for i := range p.Upstreams {
  1138  			newP.Upstreams[i] = *p.Upstreams[i].Copy()
  1139  		}
  1140  	}
  1141  
  1142  	if n := len(p.Config); n > 0 {
  1143  		newP.Config = make(map[string]interface{}, n)
  1144  
  1145  		for k, v := range p.Config {
  1146  			newP.Config[k] = v
  1147  		}
  1148  	}
  1149  
  1150  	return newP
  1151  }
  1152  
  1153  // opaqueMapsEqual compares map[string]interface{} commonly used for opaque
  1154  // config blocks. Interprets nil and {} as the same.
  1155  func opaqueMapsEqual(a, b map[string]interface{}) bool {
  1156  	if len(a) == 0 && len(b) == 0 {
  1157  		return true
  1158  	}
  1159  	return reflect.DeepEqual(a, b)
  1160  }
  1161  
  1162  // Equals returns true if the structs are recursively equal.
  1163  func (p *ConsulProxy) Equals(o *ConsulProxy) bool {
  1164  	if p == nil || o == nil {
  1165  		return p == o
  1166  	}
  1167  
  1168  	if p.LocalServiceAddress != o.LocalServiceAddress {
  1169  		return false
  1170  	}
  1171  
  1172  	if p.LocalServicePort != o.LocalServicePort {
  1173  		return false
  1174  	}
  1175  
  1176  	if !p.Expose.Equals(o.Expose) {
  1177  		return false
  1178  	}
  1179  
  1180  	if !upstreamsEquals(p.Upstreams, o.Upstreams) {
  1181  		return false
  1182  	}
  1183  
  1184  	if !opaqueMapsEqual(p.Config, o.Config) {
  1185  		return false
  1186  	}
  1187  
  1188  	return true
  1189  }
  1190  
  1191  // ConsulUpstream represents a Consul Connect upstream jobspec stanza.
  1192  type ConsulUpstream struct {
  1193  	// DestinationName is the name of the upstream service.
  1194  	DestinationName string
  1195  
  1196  	// LocalBindPort is the port the proxy will receive connections for the
  1197  	// upstream on.
  1198  	LocalBindPort int
  1199  
  1200  	// Datacenter is the datacenter in which to issue the discovery query to.
  1201  	Datacenter string
  1202  
  1203  	// LocalBindAddress is the address the proxy will receive connections for the
  1204  	// upstream on.
  1205  	LocalBindAddress string
  1206  }
  1207  
  1208  func upstreamsEquals(a, b []ConsulUpstream) bool {
  1209  	if len(a) != len(b) {
  1210  		return false
  1211  	}
  1212  
  1213  LOOP: // order does not matter
  1214  	for _, upA := range a {
  1215  		for _, upB := range b {
  1216  			if upA.Equals(&upB) {
  1217  				continue LOOP
  1218  			}
  1219  		}
  1220  		return false
  1221  	}
  1222  	return true
  1223  }
  1224  
  1225  // Copy the stanza recursively. Returns nil if u is nil.
  1226  func (u *ConsulUpstream) Copy() *ConsulUpstream {
  1227  	if u == nil {
  1228  		return nil
  1229  	}
  1230  
  1231  	return &ConsulUpstream{
  1232  		DestinationName:  u.DestinationName,
  1233  		LocalBindPort:    u.LocalBindPort,
  1234  		Datacenter:       u.Datacenter,
  1235  		LocalBindAddress: u.LocalBindAddress,
  1236  	}
  1237  }
  1238  
  1239  // Equals returns true if the structs are recursively equal.
  1240  func (u *ConsulUpstream) Equals(o *ConsulUpstream) bool {
  1241  	if u == nil || o == nil {
  1242  		return u == o
  1243  	}
  1244  
  1245  	return (*u) == (*o)
  1246  }
  1247  
  1248  // ExposeConfig represents a Consul Connect expose jobspec stanza.
  1249  type ConsulExposeConfig struct {
  1250  	// Use json tag to match with field name in api/
  1251  	Paths []ConsulExposePath `json:"Path"`
  1252  }
  1253  
  1254  type ConsulExposePath struct {
  1255  	Path          string
  1256  	Protocol      string
  1257  	LocalPathPort int
  1258  	ListenerPort  string
  1259  }
  1260  
  1261  func exposePathsEqual(pathsA, pathsB []ConsulExposePath) bool {
  1262  	if len(pathsA) != len(pathsB) {
  1263  		return false
  1264  	}
  1265  
  1266  LOOP: // order does not matter
  1267  	for _, pathA := range pathsA {
  1268  		for _, pathB := range pathsB {
  1269  			if pathA == pathB {
  1270  				continue LOOP
  1271  			}
  1272  		}
  1273  		return false
  1274  	}
  1275  	return true
  1276  }
  1277  
  1278  // Copy the stanza. Returns nil if e is nil.
  1279  func (e *ConsulExposeConfig) Copy() *ConsulExposeConfig {
  1280  	if e == nil {
  1281  		return nil
  1282  	}
  1283  	paths := make([]ConsulExposePath, len(e.Paths))
  1284  	for i := 0; i < len(e.Paths); i++ {
  1285  		paths[i] = e.Paths[i]
  1286  	}
  1287  	return &ConsulExposeConfig{
  1288  		Paths: paths,
  1289  	}
  1290  }
  1291  
  1292  // Equals returns true if the structs are recursively equal.
  1293  func (e *ConsulExposeConfig) Equals(o *ConsulExposeConfig) bool {
  1294  	if e == nil || o == nil {
  1295  		return e == o
  1296  	}
  1297  	return exposePathsEqual(e.Paths, o.Paths)
  1298  }
  1299  
  1300  // ConsulGateway is used to configure one of the Consul Connect Gateway types.
  1301  type ConsulGateway struct {
  1302  	// Proxy is used to configure the Envoy instance acting as the gateway.
  1303  	Proxy *ConsulGatewayProxy
  1304  
  1305  	// Ingress represents the Consul Configuration Entry for an Ingress Gateway.
  1306  	Ingress *ConsulIngressConfigEntry
  1307  
  1308  	// Terminating represents the Consul Configuration Entry for a Terminating Gateway.
  1309  	Terminating *ConsulTerminatingConfigEntry
  1310  
  1311  	// Mesh is not yet supported.
  1312  	// Mesh *ConsulMeshConfigEntry
  1313  }
  1314  
  1315  func (g *ConsulGateway) Prefix() string {
  1316  	switch {
  1317  	case g.Ingress != nil:
  1318  		return ConnectIngressPrefix
  1319  	default:
  1320  		return ConnectTerminatingPrefix
  1321  	}
  1322  	// also mesh
  1323  }
  1324  
  1325  func (g *ConsulGateway) Copy() *ConsulGateway {
  1326  	if g == nil {
  1327  		return nil
  1328  	}
  1329  
  1330  	return &ConsulGateway{
  1331  		Proxy:       g.Proxy.Copy(),
  1332  		Ingress:     g.Ingress.Copy(),
  1333  		Terminating: g.Terminating.Copy(),
  1334  	}
  1335  }
  1336  
  1337  func (g *ConsulGateway) Equals(o *ConsulGateway) bool {
  1338  	if g == nil || o == nil {
  1339  		return g == o
  1340  	}
  1341  
  1342  	if !g.Proxy.Equals(o.Proxy) {
  1343  		return false
  1344  	}
  1345  
  1346  	if !g.Ingress.Equals(o.Ingress) {
  1347  		return false
  1348  	}
  1349  
  1350  	if !g.Terminating.Equals(o.Terminating) {
  1351  		return false
  1352  	}
  1353  
  1354  	return true
  1355  }
  1356  
  1357  func (g *ConsulGateway) Validate() error {
  1358  	if g == nil {
  1359  		return nil
  1360  	}
  1361  
  1362  	if err := g.Proxy.Validate(); err != nil {
  1363  		return err
  1364  	}
  1365  
  1366  	if err := g.Ingress.Validate(); err != nil {
  1367  		return err
  1368  	}
  1369  
  1370  	if err := g.Terminating.Validate(); err != nil {
  1371  		return err
  1372  	}
  1373  
  1374  	// Exactly 1 of ingress/terminating/mesh(soon) must be set.
  1375  	count := 0
  1376  	if g.Ingress != nil {
  1377  		count++
  1378  	}
  1379  	if g.Terminating != nil {
  1380  		count++
  1381  	}
  1382  	if count != 1 {
  1383  		return fmt.Errorf("One Consul Gateway Configuration Entry must be set")
  1384  	}
  1385  	return nil
  1386  }
  1387  
  1388  // ConsulGatewayBindAddress is equivalent to Consul's api/catalog.go ServiceAddress
  1389  // struct, as this is used to encode values to pass along to Envoy (i.e. via
  1390  // JSON encoding).
  1391  type ConsulGatewayBindAddress struct {
  1392  	Address string
  1393  	Port    int
  1394  }
  1395  
  1396  func (a *ConsulGatewayBindAddress) Equals(o *ConsulGatewayBindAddress) bool {
  1397  	if a == nil || o == nil {
  1398  		return a == o
  1399  	}
  1400  
  1401  	if a.Address != o.Address {
  1402  		return false
  1403  	}
  1404  
  1405  	if a.Port != o.Port {
  1406  		return false
  1407  	}
  1408  
  1409  	return true
  1410  }
  1411  
  1412  func (a *ConsulGatewayBindAddress) Copy() *ConsulGatewayBindAddress {
  1413  	if a == nil {
  1414  		return nil
  1415  	}
  1416  
  1417  	return &ConsulGatewayBindAddress{
  1418  		Address: a.Address,
  1419  		Port:    a.Port,
  1420  	}
  1421  }
  1422  
  1423  func (a *ConsulGatewayBindAddress) Validate() error {
  1424  	if a == nil {
  1425  		return nil
  1426  	}
  1427  
  1428  	if a.Address == "" {
  1429  		return fmt.Errorf("Consul Gateway Bind Address must be set")
  1430  	}
  1431  
  1432  	if a.Port <= 0 && a.Port != -1 { // port -1 => nomad autofill
  1433  		return fmt.Errorf("Consul Gateway Bind Address must set valid Port")
  1434  	}
  1435  
  1436  	return nil
  1437  }
  1438  
  1439  // ConsulGatewayProxy is used to tune parameters of the proxy instance acting as
  1440  // one of the forms of Connect gateways that Consul supports.
  1441  //
  1442  // https://www.consul.io/docs/connect/proxies/envoy#gateway-options
  1443  type ConsulGatewayProxy struct {
  1444  	ConnectTimeout                  *time.Duration
  1445  	EnvoyGatewayBindTaggedAddresses bool
  1446  	EnvoyGatewayBindAddresses       map[string]*ConsulGatewayBindAddress
  1447  	EnvoyGatewayNoDefaultBind       bool
  1448  	EnvoyDNSDiscoveryType           string
  1449  	Config                          map[string]interface{}
  1450  }
  1451  
  1452  func (p *ConsulGatewayProxy) Copy() *ConsulGatewayProxy {
  1453  	if p == nil {
  1454  		return nil
  1455  	}
  1456  
  1457  	return &ConsulGatewayProxy{
  1458  		ConnectTimeout:                  helper.TimeToPtr(*p.ConnectTimeout),
  1459  		EnvoyGatewayBindTaggedAddresses: p.EnvoyGatewayBindTaggedAddresses,
  1460  		EnvoyGatewayBindAddresses:       p.copyBindAddresses(),
  1461  		EnvoyGatewayNoDefaultBind:       p.EnvoyGatewayNoDefaultBind,
  1462  		EnvoyDNSDiscoveryType:           p.EnvoyDNSDiscoveryType,
  1463  		Config:                          helper.CopyMapStringInterface(p.Config),
  1464  	}
  1465  }
  1466  
  1467  func (p *ConsulGatewayProxy) copyBindAddresses() map[string]*ConsulGatewayBindAddress {
  1468  	if p.EnvoyGatewayBindAddresses == nil {
  1469  		return nil
  1470  	}
  1471  
  1472  	bindAddresses := make(map[string]*ConsulGatewayBindAddress, len(p.EnvoyGatewayBindAddresses))
  1473  	for k, v := range p.EnvoyGatewayBindAddresses {
  1474  		bindAddresses[k] = v.Copy()
  1475  	}
  1476  
  1477  	return bindAddresses
  1478  }
  1479  
  1480  func (p *ConsulGatewayProxy) equalBindAddresses(o map[string]*ConsulGatewayBindAddress) bool {
  1481  	if len(p.EnvoyGatewayBindAddresses) != len(o) {
  1482  		return false
  1483  	}
  1484  
  1485  	for listener, addr := range p.EnvoyGatewayBindAddresses {
  1486  		if !o[listener].Equals(addr) {
  1487  			return false
  1488  		}
  1489  	}
  1490  
  1491  	return true
  1492  }
  1493  
  1494  func (p *ConsulGatewayProxy) Equals(o *ConsulGatewayProxy) bool {
  1495  	if p == nil || o == nil {
  1496  		return p == o
  1497  	}
  1498  
  1499  	if !helper.CompareTimePtrs(p.ConnectTimeout, o.ConnectTimeout) {
  1500  		return false
  1501  	}
  1502  
  1503  	if p.EnvoyGatewayBindTaggedAddresses != o.EnvoyGatewayBindTaggedAddresses {
  1504  		return false
  1505  	}
  1506  
  1507  	if !p.equalBindAddresses(o.EnvoyGatewayBindAddresses) {
  1508  		return false
  1509  	}
  1510  
  1511  	if p.EnvoyGatewayNoDefaultBind != o.EnvoyGatewayNoDefaultBind {
  1512  		return false
  1513  	}
  1514  
  1515  	if p.EnvoyDNSDiscoveryType != o.EnvoyDNSDiscoveryType {
  1516  		return false
  1517  	}
  1518  
  1519  	if !opaqueMapsEqual(p.Config, o.Config) {
  1520  		return false
  1521  	}
  1522  
  1523  	return true
  1524  }
  1525  
  1526  const (
  1527  	strictDNS  = "STRICT_DNS"
  1528  	logicalDNS = "LOGICAL_DNS"
  1529  )
  1530  
  1531  func (p *ConsulGatewayProxy) Validate() error {
  1532  	if p == nil {
  1533  		return nil
  1534  	}
  1535  
  1536  	if p.ConnectTimeout == nil {
  1537  		return fmt.Errorf("Consul Gateway Proxy connection_timeout must be set")
  1538  	}
  1539  
  1540  	switch p.EnvoyDNSDiscoveryType {
  1541  	case "", strictDNS, logicalDNS:
  1542  		// Consul defaults to logical DNS, suitable for large scale workloads.
  1543  		// https://www.envoyproxy.io/docs/envoy/v1.16.1/intro/arch_overview/upstream/service_discovery
  1544  	default:
  1545  		return fmt.Errorf("Consul Gateway Proxy Envoy DNS Discovery type must be %s or %s", strictDNS, logicalDNS)
  1546  	}
  1547  
  1548  	for _, bindAddr := range p.EnvoyGatewayBindAddresses {
  1549  		if err := bindAddr.Validate(); err != nil {
  1550  			return err
  1551  		}
  1552  	}
  1553  
  1554  	return nil
  1555  }
  1556  
  1557  // ConsulGatewayTLSConfig is used to configure TLS for a gateway.
  1558  type ConsulGatewayTLSConfig struct {
  1559  	Enabled bool
  1560  }
  1561  
  1562  func (c *ConsulGatewayTLSConfig) Copy() *ConsulGatewayTLSConfig {
  1563  	if c == nil {
  1564  		return nil
  1565  	}
  1566  
  1567  	return &ConsulGatewayTLSConfig{
  1568  		Enabled: c.Enabled,
  1569  	}
  1570  }
  1571  
  1572  func (c *ConsulGatewayTLSConfig) Equals(o *ConsulGatewayTLSConfig) bool {
  1573  	if c == nil || o == nil {
  1574  		return c == o
  1575  	}
  1576  
  1577  	return c.Enabled == o.Enabled
  1578  }
  1579  
  1580  // ConsulIngressService is used to configure a service fronted by the ingress gateway.
  1581  type ConsulIngressService struct {
  1582  	// Namespace is not yet supported.
  1583  	// Namespace string
  1584  
  1585  	Name string
  1586  
  1587  	Hosts []string
  1588  }
  1589  
  1590  func (s *ConsulIngressService) Copy() *ConsulIngressService {
  1591  	if s == nil {
  1592  		return nil
  1593  	}
  1594  
  1595  	var hosts []string = nil
  1596  	if n := len(s.Hosts); n > 0 {
  1597  		hosts = make([]string, n)
  1598  		copy(hosts, s.Hosts)
  1599  	}
  1600  
  1601  	return &ConsulIngressService{
  1602  		Name:  s.Name,
  1603  		Hosts: hosts,
  1604  	}
  1605  }
  1606  
  1607  func (s *ConsulIngressService) Equals(o *ConsulIngressService) bool {
  1608  	if s == nil || o == nil {
  1609  		return s == o
  1610  	}
  1611  
  1612  	if s.Name != o.Name {
  1613  		return false
  1614  	}
  1615  
  1616  	return helper.CompareSliceSetString(s.Hosts, o.Hosts)
  1617  }
  1618  
  1619  func (s *ConsulIngressService) Validate(isHTTP bool) error {
  1620  	if s == nil {
  1621  		return nil
  1622  	}
  1623  
  1624  	if s.Name == "" {
  1625  		return fmt.Errorf("Consul Ingress Service requires a name")
  1626  	}
  1627  
  1628  	if isHTTP && len(s.Hosts) == 0 {
  1629  		return fmt.Errorf("Consul Ingress Service requires one or more hosts when using HTTP protocol")
  1630  	} else if !isHTTP && len(s.Hosts) > 0 {
  1631  		return fmt.Errorf("Consul Ingress Service supports hosts only when using HTTP protocol")
  1632  	}
  1633  
  1634  	return nil
  1635  }
  1636  
  1637  // ConsulIngressListener is used to configure a listener on a Consul Ingress
  1638  // Gateway.
  1639  type ConsulIngressListener struct {
  1640  	Port     int
  1641  	Protocol string
  1642  	Services []*ConsulIngressService
  1643  }
  1644  
  1645  func (l *ConsulIngressListener) Copy() *ConsulIngressListener {
  1646  	if l == nil {
  1647  		return nil
  1648  	}
  1649  
  1650  	var services []*ConsulIngressService = nil
  1651  	if n := len(l.Services); n > 0 {
  1652  		services = make([]*ConsulIngressService, n)
  1653  		for i := 0; i < n; i++ {
  1654  			services[i] = l.Services[i].Copy()
  1655  		}
  1656  	}
  1657  
  1658  	return &ConsulIngressListener{
  1659  		Port:     l.Port,
  1660  		Protocol: l.Protocol,
  1661  		Services: services,
  1662  	}
  1663  }
  1664  
  1665  func (l *ConsulIngressListener) Equals(o *ConsulIngressListener) bool {
  1666  	if l == nil || o == nil {
  1667  		return l == o
  1668  	}
  1669  
  1670  	if l.Port != o.Port {
  1671  		return false
  1672  	}
  1673  
  1674  	if l.Protocol != o.Protocol {
  1675  		return false
  1676  	}
  1677  
  1678  	return ingressServicesEqual(l.Services, o.Services)
  1679  }
  1680  
  1681  func (l *ConsulIngressListener) Validate() error {
  1682  	if l == nil {
  1683  		return nil
  1684  	}
  1685  
  1686  	if l.Port <= 0 {
  1687  		return fmt.Errorf("Consul Ingress Listener requires valid Port")
  1688  	}
  1689  
  1690  	protocols := []string{"http", "tcp"}
  1691  	if !helper.SliceStringContains(protocols, l.Protocol) {
  1692  		return fmt.Errorf(`Consul Ingress Listener requires protocol of "http" or "tcp", got %q`, l.Protocol)
  1693  	}
  1694  
  1695  	if len(l.Services) == 0 {
  1696  		return fmt.Errorf("Consul Ingress Listener requires one or more services")
  1697  	}
  1698  
  1699  	for _, service := range l.Services {
  1700  		if err := service.Validate(l.Protocol == "http"); err != nil {
  1701  			return err
  1702  		}
  1703  	}
  1704  
  1705  	return nil
  1706  }
  1707  
  1708  func ingressServicesEqual(servicesA, servicesB []*ConsulIngressService) bool {
  1709  	if len(servicesA) != len(servicesB) {
  1710  		return false
  1711  	}
  1712  
  1713  COMPARE: // order does not matter
  1714  	for _, serviceA := range servicesA {
  1715  		for _, serviceB := range servicesB {
  1716  			if serviceA.Equals(serviceB) {
  1717  				continue COMPARE
  1718  			}
  1719  		}
  1720  		return false
  1721  	}
  1722  	return true
  1723  }
  1724  
  1725  // ConsulIngressConfigEntry represents the Consul Configuration Entry type for
  1726  // an Ingress Gateway.
  1727  //
  1728  // https://www.consul.io/docs/agent/config-entries/ingress-gateway#available-fields
  1729  type ConsulIngressConfigEntry struct {
  1730  	// Namespace is not yet supported.
  1731  	// Namespace string
  1732  
  1733  	TLS       *ConsulGatewayTLSConfig
  1734  	Listeners []*ConsulIngressListener
  1735  }
  1736  
  1737  func (e *ConsulIngressConfigEntry) Copy() *ConsulIngressConfigEntry {
  1738  	if e == nil {
  1739  		return nil
  1740  	}
  1741  
  1742  	var listeners []*ConsulIngressListener = nil
  1743  	if n := len(e.Listeners); n > 0 {
  1744  		listeners = make([]*ConsulIngressListener, n)
  1745  		for i := 0; i < n; i++ {
  1746  			listeners[i] = e.Listeners[i].Copy()
  1747  		}
  1748  	}
  1749  
  1750  	return &ConsulIngressConfigEntry{
  1751  		TLS:       e.TLS.Copy(),
  1752  		Listeners: listeners,
  1753  	}
  1754  }
  1755  
  1756  func (e *ConsulIngressConfigEntry) Equals(o *ConsulIngressConfigEntry) bool {
  1757  	if e == nil || o == nil {
  1758  		return e == o
  1759  	}
  1760  
  1761  	if !e.TLS.Equals(o.TLS) {
  1762  		return false
  1763  	}
  1764  
  1765  	return ingressListenersEqual(e.Listeners, o.Listeners)
  1766  }
  1767  
  1768  func (e *ConsulIngressConfigEntry) Validate() error {
  1769  	if e == nil {
  1770  		return nil
  1771  	}
  1772  
  1773  	if len(e.Listeners) == 0 {
  1774  		return fmt.Errorf("Consul Ingress Gateway requires at least one listener")
  1775  	}
  1776  
  1777  	for _, listener := range e.Listeners {
  1778  		if err := listener.Validate(); err != nil {
  1779  			return err
  1780  		}
  1781  	}
  1782  
  1783  	return nil
  1784  }
  1785  
  1786  func ingressListenersEqual(listenersA, listenersB []*ConsulIngressListener) bool {
  1787  	if len(listenersA) != len(listenersB) {
  1788  		return false
  1789  	}
  1790  
  1791  COMPARE: // order does not matter
  1792  	for _, listenerA := range listenersA {
  1793  		for _, listenerB := range listenersB {
  1794  			if listenerA.Equals(listenerB) {
  1795  				continue COMPARE
  1796  			}
  1797  		}
  1798  		return false
  1799  	}
  1800  	return true
  1801  }
  1802  
  1803  type ConsulLinkedService struct {
  1804  	Name     string
  1805  	CAFile   string
  1806  	CertFile string
  1807  	KeyFile  string
  1808  	SNI      string
  1809  }
  1810  
  1811  func (s *ConsulLinkedService) Copy() *ConsulLinkedService {
  1812  	if s == nil {
  1813  		return nil
  1814  	}
  1815  
  1816  	return &ConsulLinkedService{
  1817  		Name:     s.Name,
  1818  		CAFile:   s.CAFile,
  1819  		CertFile: s.CertFile,
  1820  		KeyFile:  s.KeyFile,
  1821  		SNI:      s.SNI,
  1822  	}
  1823  }
  1824  
  1825  func (s *ConsulLinkedService) Equals(o *ConsulLinkedService) bool {
  1826  	if s == nil || o == nil {
  1827  		return s == o
  1828  	}
  1829  
  1830  	switch {
  1831  	case s.Name != o.Name:
  1832  		return false
  1833  	case s.CAFile != o.CAFile:
  1834  		return false
  1835  	case s.CertFile != o.CertFile:
  1836  		return false
  1837  	case s.KeyFile != o.KeyFile:
  1838  		return false
  1839  	case s.SNI != o.SNI:
  1840  		return false
  1841  	}
  1842  
  1843  	return true
  1844  }
  1845  
  1846  func (s *ConsulLinkedService) Validate() error {
  1847  	if s == nil {
  1848  		return nil
  1849  	}
  1850  
  1851  	if s.Name == "" {
  1852  		return fmt.Errorf("Consul Linked Service requires Name")
  1853  	}
  1854  
  1855  	caSet := s.CAFile != ""
  1856  	certSet := s.CertFile != ""
  1857  	keySet := s.KeyFile != ""
  1858  	sniSet := s.SNI != ""
  1859  
  1860  	if (certSet || keySet) && !caSet {
  1861  		return fmt.Errorf("Consul Linked Service TLS requires CAFile")
  1862  	}
  1863  
  1864  	if certSet != keySet {
  1865  		return fmt.Errorf("Consul Linked Service TLS Cert and Key must both be set")
  1866  	}
  1867  
  1868  	if sniSet && !caSet {
  1869  		return fmt.Errorf("Consul Linked Service TLS SNI requires CAFile")
  1870  	}
  1871  
  1872  	return nil
  1873  }
  1874  
  1875  func linkedServicesEqual(servicesA, servicesB []*ConsulLinkedService) bool {
  1876  	if len(servicesA) != len(servicesB) {
  1877  		return false
  1878  	}
  1879  
  1880  COMPARE: // order does not matter
  1881  	for _, serviceA := range servicesA {
  1882  		for _, serviceB := range servicesB {
  1883  			if serviceA.Equals(serviceB) {
  1884  				continue COMPARE
  1885  			}
  1886  		}
  1887  		return false
  1888  	}
  1889  	return true
  1890  }
  1891  
  1892  type ConsulTerminatingConfigEntry struct {
  1893  	// Namespace is not yet supported.
  1894  	// Namespace string
  1895  
  1896  	Services []*ConsulLinkedService
  1897  }
  1898  
  1899  func (e *ConsulTerminatingConfigEntry) Copy() *ConsulTerminatingConfigEntry {
  1900  	if e == nil {
  1901  		return nil
  1902  	}
  1903  
  1904  	var services []*ConsulLinkedService = nil
  1905  	if n := len(e.Services); n > 0 {
  1906  		services = make([]*ConsulLinkedService, n)
  1907  		for i := 0; i < n; i++ {
  1908  			services[i] = e.Services[i].Copy()
  1909  		}
  1910  	}
  1911  
  1912  	return &ConsulTerminatingConfigEntry{
  1913  		Services: services,
  1914  	}
  1915  }
  1916  
  1917  func (e *ConsulTerminatingConfigEntry) Equals(o *ConsulTerminatingConfigEntry) bool {
  1918  	if e == nil || o == nil {
  1919  		return e == o
  1920  	}
  1921  
  1922  	return linkedServicesEqual(e.Services, o.Services)
  1923  }
  1924  
  1925  func (e *ConsulTerminatingConfigEntry) Validate() error {
  1926  	if e == nil {
  1927  		return nil
  1928  	}
  1929  
  1930  	if len(e.Services) == 0 {
  1931  		return fmt.Errorf("Consul Terminating Gateway requires at least one service")
  1932  	}
  1933  
  1934  	for _, service := range e.Services {
  1935  		if err := service.Validate(); err != nil {
  1936  			return err
  1937  		}
  1938  	}
  1939  
  1940  	return nil
  1941  }