github.com/hernad/nomad@v1.6.112/nomad/structs/services.go (about)

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