github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/config/config.go (about)

     1  package config
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"reflect"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/hashicorp/consul-template/config"
    12  	"github.com/hashicorp/nomad/client/lib/cgutil"
    13  	"github.com/hashicorp/nomad/command/agent/host"
    14  	"golang.org/x/exp/maps"
    15  	"golang.org/x/exp/slices"
    16  
    17  	log "github.com/hashicorp/go-hclog"
    18  	"github.com/hashicorp/nomad/client/state"
    19  	"github.com/hashicorp/nomad/helper/bufconndialer"
    20  	"github.com/hashicorp/nomad/helper/pluginutils/loader"
    21  	"github.com/hashicorp/nomad/helper/pointer"
    22  	"github.com/hashicorp/nomad/nomad/structs"
    23  	structsc "github.com/hashicorp/nomad/nomad/structs/config"
    24  	"github.com/hashicorp/nomad/plugins/base"
    25  	"github.com/hashicorp/nomad/version"
    26  )
    27  
    28  var (
    29  	// DefaultEnvDenylist is the default set of environment variables that are
    30  	// filtered when passing the environment variables of the host to a task.
    31  	DefaultEnvDenylist = strings.Join(host.DefaultEnvDenyList, ",")
    32  
    33  	// DefaultUserDenylist is the default set of users that tasks are not
    34  	// allowed to run as when using a driver in "user.checked_drivers"
    35  	DefaultUserDenylist = strings.Join([]string{
    36  		"root",
    37  		"Administrator",
    38  	}, ",")
    39  
    40  	// DefaultUserCheckedDrivers is the set of drivers we apply the user
    41  	// denylist onto. For virtualized drivers it often doesn't make sense to
    42  	// make this stipulation so by default they are ignored.
    43  	DefaultUserCheckedDrivers = strings.Join([]string{
    44  		"exec",
    45  		"qemu",
    46  		"java",
    47  	}, ",")
    48  
    49  	// DefaultChrootEnv is a mapping of directories on the host OS to attempt to embed inside each
    50  	// task's chroot.
    51  	DefaultChrootEnv = map[string]string{
    52  		"/bin":            "/bin",
    53  		"/etc":            "/etc",
    54  		"/lib":            "/lib",
    55  		"/lib32":          "/lib32",
    56  		"/lib64":          "/lib64",
    57  		"/run/resolvconf": "/run/resolvconf",
    58  		"/sbin":           "/sbin",
    59  		"/usr":            "/usr",
    60  
    61  		// embed systemd-resolved paths for systemd-resolved paths:
    62  		// /etc/resolv.conf is a symlink to /run/systemd/resolve/stub-resolv.conf in such systems.
    63  		// In non-systemd systems, this mount is a no-op and the path is ignored if not present.
    64  		"/run/systemd/resolve": "/run/systemd/resolve",
    65  	}
    66  
    67  	DefaultTemplateMaxStale = 87600 * time.Hour
    68  
    69  	DefaultTemplateFunctionDenylist = []string{"plugin", "writeToFile"}
    70  )
    71  
    72  // RPCHandler can be provided to the Client if there is a local server
    73  // to avoid going over the network. If not provided, the Client will
    74  // maintain a connection pool to the servers
    75  type RPCHandler interface {
    76  	RPC(method string, args interface{}, reply interface{}) error
    77  }
    78  
    79  // Config is used to parameterize and configure the behavior of the client
    80  type Config struct {
    81  	// DevMode controls if we are in a development mode which
    82  	// avoids persistent storage.
    83  	DevMode bool
    84  
    85  	// EnableDebug is used to enable debugging RPC endpoints
    86  	// in the absence of ACLs
    87  	EnableDebug bool
    88  
    89  	// StateDir is where we store our state
    90  	StateDir string
    91  
    92  	// AllocDir is where we store data for allocations
    93  	AllocDir string
    94  
    95  	// Logger provides a logger to the client
    96  	Logger log.InterceptLogger
    97  
    98  	// Region is the clients region
    99  	Region string
   100  
   101  	// Network interface to be used in network fingerprinting
   102  	NetworkInterface string
   103  
   104  	// Network speed is the default speed of network interfaces if they can not
   105  	// be determined dynamically.
   106  	NetworkSpeed int
   107  
   108  	// CpuCompute is the default total CPU compute if they can not be determined
   109  	// dynamically. It should be given as Cores * MHz (2 Cores * 2 Ghz = 4000)
   110  	CpuCompute int
   111  
   112  	// MemoryMB is the default node total memory in megabytes if it cannot be
   113  	// determined dynamically.
   114  	MemoryMB int
   115  
   116  	// MaxKillTimeout allows capping the user-specifiable KillTimeout. If the
   117  	// task's KillTimeout is greater than the MaxKillTimeout, MaxKillTimeout is
   118  	// used.
   119  	MaxKillTimeout time.Duration
   120  
   121  	// Servers is a list of known server addresses. These are as "host:port"
   122  	Servers []string
   123  
   124  	// RPCHandler can be provided to avoid network traffic if the
   125  	// server is running locally.
   126  	RPCHandler RPCHandler
   127  
   128  	// Node provides the base node
   129  	Node *structs.Node
   130  
   131  	// ClientMaxPort is the upper range of the ports that the client uses for
   132  	// communicating with plugin subsystems over loopback
   133  	ClientMaxPort uint
   134  
   135  	// ClientMinPort is the lower range of the ports that the client uses for
   136  	// communicating with plugin subsystems over loopback
   137  	ClientMinPort uint
   138  
   139  	// MaxDynamicPort is the largest dynamic port generated
   140  	MaxDynamicPort int
   141  
   142  	// MinDynamicPort is the smallest dynamic port generated
   143  	MinDynamicPort int
   144  
   145  	// A mapping of directories on the host OS to attempt to embed inside each
   146  	// task's chroot.
   147  	ChrootEnv map[string]string
   148  
   149  	// Options provides arbitrary key-value configuration for nomad internals,
   150  	// like fingerprinters and drivers. The format is:
   151  	//
   152  	//	namespace.option = value
   153  	Options map[string]string
   154  
   155  	// Version is the version of the Nomad client
   156  	Version *version.VersionInfo
   157  
   158  	// ConsulConfig is this Agent's Consul configuration
   159  	ConsulConfig *structsc.ConsulConfig
   160  
   161  	// VaultConfig is this Agent's Vault configuration
   162  	VaultConfig *structsc.VaultConfig
   163  
   164  	// StatsCollectionInterval is the interval at which the Nomad client
   165  	// collects resource usage stats
   166  	StatsCollectionInterval time.Duration
   167  
   168  	// PublishNodeMetrics determines whether nomad is going to publish node
   169  	// level metrics to remote Telemetry sinks
   170  	PublishNodeMetrics bool
   171  
   172  	// PublishAllocationMetrics determines whether nomad is going to publish
   173  	// allocation metrics to remote Telemetry sinks
   174  	PublishAllocationMetrics bool
   175  
   176  	// TLSConfig holds various TLS related configurations
   177  	TLSConfig *structsc.TLSConfig
   178  
   179  	// GCInterval is the time interval at which the client triggers garbage
   180  	// collection
   181  	GCInterval time.Duration
   182  
   183  	// GCParallelDestroys is the number of parallel destroys the garbage
   184  	// collector will allow.
   185  	GCParallelDestroys int
   186  
   187  	// GCDiskUsageThreshold is the disk usage threshold given as a percent
   188  	// beyond which the Nomad client triggers GC of terminal allocations
   189  	GCDiskUsageThreshold float64
   190  
   191  	// GCInodeUsageThreshold is the inode usage threshold given as a percent
   192  	// beyond which the Nomad client triggers GC of the terminal allocations
   193  	GCInodeUsageThreshold float64
   194  
   195  	// GCMaxAllocs is the maximum number of allocations a node can have
   196  	// before garbage collection is triggered.
   197  	GCMaxAllocs int
   198  
   199  	// NoHostUUID disables using the host's UUID and will force generation of a
   200  	// random UUID.
   201  	NoHostUUID bool
   202  
   203  	// ACLEnabled controls if ACL enforcement and management is enabled.
   204  	ACLEnabled bool
   205  
   206  	// ACLTokenTTL is how long we cache token values for
   207  	ACLTokenTTL time.Duration
   208  
   209  	// ACLPolicyTTL is how long we cache policy values for
   210  	ACLPolicyTTL time.Duration
   211  
   212  	// ACLRoleTTL is how long we cache ACL role value for within each Nomad
   213  	// client.
   214  	ACLRoleTTL time.Duration
   215  
   216  	// DisableRemoteExec disables remote exec targeting tasks on this client
   217  	DisableRemoteExec bool
   218  
   219  	// TemplateConfig includes configuration for template rendering
   220  	TemplateConfig *ClientTemplateConfig
   221  
   222  	// RPCHoldTimeout is how long an RPC can be "held" before it is errored.
   223  	// This is used to paper over a loss of leadership by instead holding RPCs,
   224  	// so that the caller experiences a slow response rather than an error.
   225  	// This period is meant to be long enough for a leader election to take
   226  	// place, and a small jitter is applied to avoid a thundering herd.
   227  	RPCHoldTimeout time.Duration
   228  
   229  	// PluginLoader is used to load plugins.
   230  	PluginLoader loader.PluginCatalog
   231  
   232  	// PluginSingletonLoader is a plugin loader that will returns singleton
   233  	// instances of the plugins.
   234  	PluginSingletonLoader loader.PluginCatalog
   235  
   236  	// StateDBFactory is used to override stateDB implementations,
   237  	StateDBFactory state.NewStateDBFunc
   238  
   239  	// CNIPath is the path used to search for CNI plugins. Multiple paths can
   240  	// be specified with colon delimited
   241  	CNIPath string
   242  
   243  	// CNIConfigDir is the directory where CNI network configuration is located. The
   244  	// client will use this path when fingerprinting CNI networks.
   245  	CNIConfigDir string
   246  
   247  	// CNIInterfacePrefix is the prefix to use when creating CNI network interfaces. This
   248  	// defaults to 'eth', therefore the first interface created by CNI inside the alloc
   249  	// network will be 'eth0'.
   250  	CNIInterfacePrefix string
   251  
   252  	// BridgeNetworkName is the name to use for the bridge created in bridge
   253  	// networking mode. This defaults to 'nomad' if not set
   254  	BridgeNetworkName string
   255  
   256  	// BridgeNetworkAllocSubnet is the IP subnet to use for address allocation
   257  	// for allocations in bridge networking mode. Subnet must be in CIDR
   258  	// notation
   259  	BridgeNetworkAllocSubnet string
   260  
   261  	// HostVolumes is a map of the configured host volumes by name.
   262  	HostVolumes map[string]*structs.ClientHostVolumeConfig
   263  
   264  	// HostNetworks is a map of the conigured host networks by name.
   265  	HostNetworks map[string]*structs.ClientHostNetworkConfig
   266  
   267  	// BindWildcardDefaultHostNetwork toggles if the default host network should accept all
   268  	// destinations (true) or only filter on the IP of the default host network (false) when
   269  	// port mapping. This allows Nomad clients with no defined host networks to accept and
   270  	// port forward traffic only matching on the destination port. An example use of this
   271  	// is when a network loadbalancer is utilizing direct server return and the destination
   272  	// address of incomming packets does not match the IP address of the host interface.
   273  	//
   274  	// This configuration is only considered if no host networks are defined.
   275  	BindWildcardDefaultHostNetwork bool
   276  
   277  	// CgroupParent is the parent cgroup Nomad should use when managing any cgroup subsystems.
   278  	// Currently this only includes the 'cpuset' cgroup subsystem.
   279  	CgroupParent string
   280  
   281  	// ReservableCores if set overrides the set of reservable cores reported in fingerprinting.
   282  	ReservableCores []uint16
   283  
   284  	// NomadServiceDiscovery determines whether the Nomad native service
   285  	// discovery client functionality is enabled.
   286  	NomadServiceDiscovery bool
   287  
   288  	// TemplateDialer is our custom HTTP dialer for consul-template. This is
   289  	// used for template functions which require access to the Nomad API.
   290  	TemplateDialer *bufconndialer.BufConnWrapper
   291  
   292  	// Artifact configuration from the agent's config file.
   293  	Artifact *ArtifactConfig
   294  }
   295  
   296  // ClientTemplateConfig is configuration on the client specific to template
   297  // rendering
   298  type ClientTemplateConfig struct {
   299  	// FunctionDenylist disables functions in consul-template that
   300  	// are unsafe because they expose information from the client host.
   301  	FunctionDenylist []string `hcl:"function_denylist"`
   302  
   303  	// Deprecated: COMPAT(1.0) consul-template uses inclusive language from
   304  	// v0.25.0 - function_blacklist is kept for compatibility
   305  	FunctionBlacklist []string `hcl:"function_blacklist"`
   306  
   307  	// DisableSandbox allows templates to access arbitrary files on the
   308  	// client host. By default templates can access files only within
   309  	// the task directory.
   310  	DisableSandbox bool `hcl:"disable_file_sandbox"`
   311  
   312  	// This is the maximum interval to allow "stale" data. By default, only the
   313  	// Consul leader will respond to queries; any requests to a follower will
   314  	// forward to the leader. In large clusters with many requests, this is not as
   315  	// scalable, so this option allows any follower to respond to a query, so long
   316  	// as the last-replicated data is within these bounds. Higher values result in
   317  	// less cluster load, but are more likely to have outdated data.
   318  	// NOTE: Since Consul Template uses a pointer, this field uses a pointer which
   319  	// is inconsistent with how Nomad typically works. This decision was made to
   320  	// maintain parity with the external subsystem, not to establish a new standard.
   321  	MaxStale    *time.Duration `hcl:"-"`
   322  	MaxStaleHCL string         `hcl:"max_stale,optional"`
   323  
   324  	// BlockQueryWaitTime is amount of time in seconds to do a blocking query for.
   325  	// Many endpoints in Consul support a feature known as "blocking queries".
   326  	// A blocking query is used to wait for a potential change using long polling.
   327  	// NOTE: Since Consul Template uses a pointer, this field uses a pointer which
   328  	// is inconsistent with how Nomad typically works. This decision was made to
   329  	// maintain parity with the external subsystem, not to establish a new standard.
   330  	BlockQueryWaitTime    *time.Duration `hcl:"-"`
   331  	BlockQueryWaitTimeHCL string         `hcl:"block_query_wait,optional"`
   332  
   333  	// Wait is the quiescence timers; it defines the minimum and maximum amount of
   334  	// time to wait for the Consul cluster to reach a consistent state before rendering a
   335  	// template. This is useful to enable in systems where Consul is experiencing
   336  	// a lot of flapping because it will reduce the number of times a template is rendered.
   337  	Wait *WaitConfig `hcl:"wait,optional" json:"-"`
   338  
   339  	// WaitBounds allows operators to define boundaries on individual template wait
   340  	// configuration overrides. If set, this ensures that if a job author specifies
   341  	// a wait configuration with values the cluster operator does not allow, the
   342  	// cluster operator's boundary will be applied rather than the job author's
   343  	// out of bounds configuration.
   344  	WaitBounds *WaitConfig `hcl:"wait_bounds,optional" json:"-"`
   345  
   346  	// This controls the retry behavior when an error is returned from Consul.
   347  	// Consul Template is highly fault tolerant, meaning it does not exit in the
   348  	// face of failure. Instead, it uses exponential back-off and retry functions
   349  	// to wait for the cluster to become available, as is customary in distributed
   350  	// systems.
   351  	ConsulRetry *RetryConfig `hcl:"consul_retry,optional"`
   352  
   353  	// This controls the retry behavior when an error is returned from Vault.
   354  	// Consul Template is highly fault tolerant, meaning it does not exit in the
   355  	// face of failure. Instead, it uses exponential back-off and retry functions
   356  	// to wait for the cluster to become available, as is customary in distributed
   357  	// systems.
   358  	VaultRetry *RetryConfig `hcl:"vault_retry,optional"`
   359  
   360  	// This controls the retry behavior when an error is returned from Nomad.
   361  	// Consul Template is highly fault tolerant, meaning it does not exit in the
   362  	// face of failure. Instead, it uses exponential back-off and retry functions
   363  	// to wait for the cluster to become available, as is customary in distributed
   364  	// systems.
   365  	NomadRetry *RetryConfig `hcl:"nomad_retry,optional"`
   366  }
   367  
   368  // Copy returns a deep copy of a ClientTemplateConfig
   369  func (c *ClientTemplateConfig) Copy() *ClientTemplateConfig {
   370  	if c == nil {
   371  		return nil
   372  	}
   373  
   374  	nc := new(ClientTemplateConfig)
   375  	*nc = *c
   376  
   377  	if len(c.FunctionDenylist) > 0 {
   378  		nc.FunctionDenylist = slices.Clone(nc.FunctionDenylist)
   379  	} else if c.FunctionDenylist != nil {
   380  		// Explicitly no functions denied (which is different than nil)
   381  		nc.FunctionDenylist = []string{}
   382  	}
   383  
   384  	if c.BlockQueryWaitTime != nil {
   385  		nc.BlockQueryWaitTime = &*c.BlockQueryWaitTime
   386  	}
   387  
   388  	if c.MaxStale != nil {
   389  		nc.MaxStale = &*c.MaxStale
   390  	}
   391  
   392  	if c.Wait != nil {
   393  		nc.Wait = c.Wait.Copy()
   394  	}
   395  
   396  	if c.ConsulRetry != nil {
   397  		nc.ConsulRetry = c.ConsulRetry.Copy()
   398  	}
   399  
   400  	if c.VaultRetry != nil {
   401  		nc.VaultRetry = c.VaultRetry.Copy()
   402  	}
   403  
   404  	if c.NomadRetry != nil {
   405  		nc.NomadRetry = c.NomadRetry.Copy()
   406  	}
   407  
   408  	return nc
   409  }
   410  
   411  func (c *ClientTemplateConfig) IsEmpty() bool {
   412  	if c == nil {
   413  		return true
   414  	}
   415  
   416  	return !c.DisableSandbox &&
   417  		c.FunctionDenylist == nil &&
   418  		c.FunctionBlacklist == nil &&
   419  		c.BlockQueryWaitTime == nil &&
   420  		c.BlockQueryWaitTimeHCL == "" &&
   421  		c.MaxStale == nil &&
   422  		c.MaxStaleHCL == "" &&
   423  		c.Wait.IsEmpty() &&
   424  		c.ConsulRetry.IsEmpty() &&
   425  		c.VaultRetry.IsEmpty() &&
   426  		c.NomadRetry.IsEmpty()
   427  }
   428  
   429  // WaitConfig is mirrored from templateconfig.WaitConfig because we need to handle
   430  // the HCL conversion which happens in agent.ParseConfigFile
   431  // NOTE: Since Consul Template requires pointers, this type uses pointers to fields
   432  // which is inconsistent with how Nomad typically works. This decision was made
   433  // to maintain parity with the external subsystem, not to establish a new standard.
   434  type WaitConfig struct {
   435  	Min    *time.Duration `hcl:"-"`
   436  	MinHCL string         `hcl:"min,optional" json:"-"`
   437  	Max    *time.Duration `hcl:"-"`
   438  	MaxHCL string         `hcl:"max,optional" json:"-"`
   439  }
   440  
   441  // Copy returns a deep copy of the receiver.
   442  func (wc *WaitConfig) Copy() *WaitConfig {
   443  	if wc == nil {
   444  		return nil
   445  	}
   446  
   447  	nwc := new(WaitConfig)
   448  
   449  	if wc.Min != nil {
   450  		nwc.Min = &*wc.Min
   451  	}
   452  
   453  	if wc.Max != nil {
   454  		nwc.Max = &*wc.Max
   455  	}
   456  
   457  	return wc
   458  }
   459  
   460  // Equal returns the result of reflect.DeepEqual
   461  func (wc *WaitConfig) Equal(other *WaitConfig) bool {
   462  	return reflect.DeepEqual(wc, other)
   463  }
   464  
   465  // IsEmpty returns true if the receiver only contains an instance with no fields set.
   466  func (wc *WaitConfig) IsEmpty() bool {
   467  	if wc == nil {
   468  		return true
   469  	}
   470  	return wc.Equal(&WaitConfig{})
   471  }
   472  
   473  // Validate returns an error  if the receiver is nil or empty or if Min is greater
   474  // than Max the user specified Max.
   475  func (wc *WaitConfig) Validate() error {
   476  	// If the config is nil or empty return false so that it is never assigned.
   477  	if wc == nil || wc.IsEmpty() {
   478  		return errors.New("wait config is nil or empty")
   479  	}
   480  
   481  	// If min is nil, return
   482  	if wc.Min == nil {
   483  		return nil
   484  	}
   485  
   486  	// If min isn't nil, make sure Max is less than Min.
   487  	if wc.Max != nil {
   488  		if *wc.Min > *wc.Max {
   489  			return fmt.Errorf("wait config min %d is greater than max %d", *wc.Min, *wc.Max)
   490  		}
   491  	}
   492  
   493  	// Otherwise, return nil. Consul Template will set a Max based off of Min.
   494  	return nil
   495  }
   496  
   497  // Merge merges two WaitConfigs. The passed instance always takes precedence.
   498  func (wc *WaitConfig) Merge(b *WaitConfig) *WaitConfig {
   499  	if wc == nil {
   500  		return b
   501  	}
   502  
   503  	result := *wc
   504  	if b == nil {
   505  		return &result
   506  	}
   507  
   508  	if b.Min != nil {
   509  		result.Min = &*b.Min
   510  	}
   511  
   512  	if b.MinHCL != "" {
   513  		result.MinHCL = b.MinHCL
   514  	}
   515  
   516  	if b.Max != nil {
   517  		result.Max = &*b.Max
   518  	}
   519  
   520  	if b.MaxHCL != "" {
   521  		result.MaxHCL = b.MaxHCL
   522  	}
   523  
   524  	return &result
   525  }
   526  
   527  // ToConsulTemplate converts a client WaitConfig instance to a consul-template WaitConfig
   528  func (wc *WaitConfig) ToConsulTemplate() (*config.WaitConfig, error) {
   529  	if wc.IsEmpty() {
   530  		return nil, errors.New("wait config is empty")
   531  	}
   532  
   533  	if err := wc.Validate(); err != nil {
   534  		return nil, err
   535  	}
   536  
   537  	result := &config.WaitConfig{Enabled: pointer.Of(true)}
   538  
   539  	if wc.Min != nil {
   540  		result.Min = wc.Min
   541  	}
   542  
   543  	if wc.Max != nil {
   544  		result.Max = wc.Max
   545  	}
   546  
   547  	return result, nil
   548  }
   549  
   550  // RetryConfig is mirrored from templateconfig.WaitConfig because we need to handle
   551  // the HCL indirection to support mapping in agent.ParseConfigFile.
   552  // NOTE: Since Consul Template requires pointers, this type uses pointers to fields
   553  // which is inconsistent with how Nomad typically works. However, since zero in
   554  // Attempts and MaxBackoff have special meaning, it is necessary to know if the
   555  // value was actually set rather than if it defaulted to 0. The rest of the fields
   556  // use pointers to maintain parity with the external subystem, not to establish
   557  // a new standard.
   558  type RetryConfig struct {
   559  	// Attempts is the total number of maximum attempts to retry before letting
   560  	// the error fall through.
   561  	// 0 means unlimited.
   562  	Attempts *int `hcl:"attempts,optional"`
   563  	// Backoff is the base of the exponential backoff. This number will be
   564  	// multiplied by the next power of 2 on each iteration.
   565  	Backoff    *time.Duration `hcl:"-"`
   566  	BackoffHCL string         `hcl:"backoff,optional" json:"-"`
   567  	// MaxBackoff is an upper limit to the sleep time between retries
   568  	// A MaxBackoff of 0 means there is no limit to the exponential growth of the backoff.
   569  	MaxBackoff    *time.Duration `hcl:"-"`
   570  	MaxBackoffHCL string         `hcl:"max_backoff,optional" json:"-"`
   571  }
   572  
   573  func (rc *RetryConfig) Copy() *RetryConfig {
   574  	if rc == nil {
   575  		return nil
   576  	}
   577  
   578  	nrc := new(RetryConfig)
   579  	*nrc = *rc
   580  
   581  	// Now copy pointer values
   582  	if rc.Attempts != nil {
   583  		nrc.Attempts = &*rc.Attempts
   584  	}
   585  	if rc.Backoff != nil {
   586  		nrc.Backoff = &*rc.Backoff
   587  	}
   588  	if rc.MaxBackoff != nil {
   589  		nrc.MaxBackoff = &*rc.MaxBackoff
   590  	}
   591  
   592  	return nrc
   593  }
   594  
   595  // Equal returns the result of reflect.DeepEqual
   596  func (rc *RetryConfig) Equal(other *RetryConfig) bool {
   597  	return reflect.DeepEqual(rc, other)
   598  }
   599  
   600  // IsEmpty returns true if the receiver only contains an instance with no fields set.
   601  func (rc *RetryConfig) IsEmpty() bool {
   602  	if rc == nil {
   603  		return true
   604  	}
   605  
   606  	return rc.Equal(&RetryConfig{})
   607  }
   608  
   609  // Validate returns an error if the receiver is nil or empty, or if Backoff
   610  // is greater than  MaxBackoff.
   611  func (rc *RetryConfig) Validate() error {
   612  	// If the config is nil or empty return false so that it is never assigned.
   613  	if rc == nil || rc.IsEmpty() {
   614  		return errors.New("retry config is nil or empty")
   615  	}
   616  
   617  	// If Backoff not set, no need to validate
   618  	if rc.Backoff == nil {
   619  		return nil
   620  	}
   621  
   622  	// MaxBackoff nil will end up defaulted to 1 minutes. We should validate that
   623  	// the user supplied backoff does not exceed that.
   624  	if rc.MaxBackoff == nil && *rc.Backoff > config.DefaultRetryMaxBackoff {
   625  		return fmt.Errorf("retry config backoff %d is greater than default max_backoff %d", *rc.Backoff, config.DefaultRetryMaxBackoff)
   626  	}
   627  
   628  	// MaxBackoff == 0 means backoff is unbounded. No need to validate.
   629  	if rc.MaxBackoff != nil && *rc.MaxBackoff == 0 {
   630  		return nil
   631  	}
   632  
   633  	if rc.MaxBackoff != nil && *rc.Backoff > *rc.MaxBackoff {
   634  		return fmt.Errorf("retry config backoff %d is greater than max_backoff %d", *rc.Backoff, *rc.MaxBackoff)
   635  	}
   636  
   637  	return nil
   638  }
   639  
   640  // Merge merges two RetryConfigs. The passed instance always takes precedence.
   641  func (rc *RetryConfig) Merge(b *RetryConfig) *RetryConfig {
   642  	if rc == nil {
   643  		return b
   644  	}
   645  
   646  	result := *rc
   647  	if b == nil {
   648  		return &result
   649  	}
   650  
   651  	if b.Attempts != nil {
   652  		result.Attempts = &*b.Attempts
   653  	}
   654  
   655  	if b.Backoff != nil {
   656  		result.Backoff = &*b.Backoff
   657  	}
   658  
   659  	if b.BackoffHCL != "" {
   660  		result.BackoffHCL = b.BackoffHCL
   661  	}
   662  
   663  	if b.MaxBackoff != nil {
   664  		result.MaxBackoff = &*b.MaxBackoff
   665  	}
   666  
   667  	if b.MaxBackoffHCL != "" {
   668  		result.MaxBackoffHCL = b.MaxBackoffHCL
   669  	}
   670  
   671  	return &result
   672  }
   673  
   674  // ToConsulTemplate converts a client RetryConfig instance to a consul-template RetryConfig
   675  func (rc *RetryConfig) ToConsulTemplate() (*config.RetryConfig, error) {
   676  	if err := rc.Validate(); err != nil {
   677  		return nil, err
   678  	}
   679  
   680  	result := &config.RetryConfig{Enabled: pointer.Of(true)}
   681  
   682  	if rc.Attempts != nil {
   683  		result.Attempts = rc.Attempts
   684  	}
   685  
   686  	if rc.Backoff != nil {
   687  		result.Backoff = rc.Backoff
   688  	}
   689  
   690  	if rc.MaxBackoff != nil {
   691  		result.MaxBackoff = &*rc.MaxBackoff
   692  	}
   693  
   694  	return result, nil
   695  }
   696  
   697  func (c *Config) Copy() *Config {
   698  	if c == nil {
   699  		return nil
   700  	}
   701  
   702  	nc := *c
   703  	nc.Node = nc.Node.Copy()
   704  	nc.Servers = slices.Clone(nc.Servers)
   705  	nc.Options = maps.Clone(nc.Options)
   706  	nc.HostVolumes = structs.CopyMapStringClientHostVolumeConfig(nc.HostVolumes)
   707  	nc.ConsulConfig = c.ConsulConfig.Copy()
   708  	nc.VaultConfig = c.VaultConfig.Copy()
   709  	nc.TemplateConfig = c.TemplateConfig.Copy()
   710  	nc.ReservableCores = slices.Clone(c.ReservableCores)
   711  	nc.Artifact = c.Artifact.Copy()
   712  	return &nc
   713  }
   714  
   715  // DefaultConfig returns the default configuration
   716  func DefaultConfig() *Config {
   717  	return &Config{
   718  		Version:                 version.GetVersion(),
   719  		VaultConfig:             structsc.DefaultVaultConfig(),
   720  		ConsulConfig:            structsc.DefaultConsulConfig(),
   721  		Region:                  "global",
   722  		StatsCollectionInterval: 1 * time.Second,
   723  		TLSConfig:               &structsc.TLSConfig{},
   724  		GCInterval:              1 * time.Minute,
   725  		GCParallelDestroys:      2,
   726  		GCDiskUsageThreshold:    80,
   727  		GCInodeUsageThreshold:   70,
   728  		GCMaxAllocs:             50,
   729  		NoHostUUID:              true,
   730  		DisableRemoteExec:       false,
   731  		TemplateConfig: &ClientTemplateConfig{
   732  			FunctionDenylist:   DefaultTemplateFunctionDenylist,
   733  			DisableSandbox:     false,
   734  			BlockQueryWaitTime: pointer.Of(5 * time.Minute),         // match Consul default
   735  			MaxStale:           pointer.Of(DefaultTemplateMaxStale), // match Consul default
   736  			Wait: &WaitConfig{
   737  				Min: pointer.Of(5 * time.Second),
   738  				Max: pointer.Of(4 * time.Minute),
   739  			},
   740  			ConsulRetry: &RetryConfig{
   741  				Attempts: pointer.Of(0), // unlimited
   742  			},
   743  			VaultRetry: &RetryConfig{
   744  				Attempts: pointer.Of(0), // unlimited
   745  			},
   746  			NomadRetry: &RetryConfig{
   747  				Attempts: pointer.Of(0), // unlimited
   748  			},
   749  		},
   750  		RPCHoldTimeout:     5 * time.Second,
   751  		CNIPath:            "/opt/cni/bin",
   752  		CNIConfigDir:       "/opt/cni/config",
   753  		CNIInterfacePrefix: "eth",
   754  		HostNetworks:       map[string]*structs.ClientHostNetworkConfig{},
   755  		CgroupParent:       cgutil.GetCgroupParent(""),
   756  		MaxDynamicPort:     structs.DefaultMinDynamicPort,
   757  		MinDynamicPort:     structs.DefaultMaxDynamicPort,
   758  	}
   759  }
   760  
   761  // Read returns the specified configuration value or "".
   762  func (c *Config) Read(id string) string {
   763  	return c.Options[id]
   764  }
   765  
   766  // ReadDefault returns the specified configuration value, or the specified
   767  // default value if none is set.
   768  func (c *Config) ReadDefault(id string, defaultValue string) string {
   769  	return c.ReadAlternativeDefault([]string{id}, defaultValue)
   770  }
   771  
   772  // ReadAlternativeDefault returns the specified configuration value, or the
   773  // specified value if none is set.
   774  func (c *Config) ReadAlternativeDefault(ids []string, defaultValue string) string {
   775  	for _, id := range ids {
   776  		val, ok := c.Options[id]
   777  		if ok {
   778  			return val
   779  		}
   780  	}
   781  
   782  	return defaultValue
   783  }
   784  
   785  // ReadBool parses the specified option as a boolean.
   786  func (c *Config) ReadBool(id string) (bool, error) {
   787  	val, ok := c.Options[id]
   788  	if !ok {
   789  		return false, fmt.Errorf("Specified config is missing from options")
   790  	}
   791  	bval, err := strconv.ParseBool(val)
   792  	if err != nil {
   793  		return false, fmt.Errorf("Failed to parse %s as bool: %s", val, err)
   794  	}
   795  	return bval, nil
   796  }
   797  
   798  // ReadBoolDefault tries to parse the specified option as a boolean. If there is
   799  // an error in parsing, the default option is returned.
   800  func (c *Config) ReadBoolDefault(id string, defaultValue bool) bool {
   801  	val, err := c.ReadBool(id)
   802  	if err != nil {
   803  		return defaultValue
   804  	}
   805  	return val
   806  }
   807  
   808  // ReadInt parses the specified option as a int.
   809  func (c *Config) ReadInt(id string) (int, error) {
   810  	val, ok := c.Options[id]
   811  	if !ok {
   812  		return 0, fmt.Errorf("Specified config is missing from options")
   813  	}
   814  	ival, err := strconv.Atoi(val)
   815  	if err != nil {
   816  		return 0, fmt.Errorf("Failed to parse %s as int: %s", val, err)
   817  	}
   818  	return ival, nil
   819  }
   820  
   821  // ReadIntDefault tries to parse the specified option as a int. If there is
   822  // an error in parsing, the default option is returned.
   823  func (c *Config) ReadIntDefault(id string, defaultValue int) int {
   824  	val, err := c.ReadInt(id)
   825  	if err != nil {
   826  		return defaultValue
   827  	}
   828  	return val
   829  }
   830  
   831  // ReadDuration parses the specified option as a duration.
   832  func (c *Config) ReadDuration(id string) (time.Duration, error) {
   833  	val, ok := c.Options[id]
   834  	if !ok {
   835  		return time.Duration(0), fmt.Errorf("Specified config is missing from options")
   836  	}
   837  	dval, err := time.ParseDuration(val)
   838  	if err != nil {
   839  		return time.Duration(0), fmt.Errorf("Failed to parse %s as time duration: %s", val, err)
   840  	}
   841  	return dval, nil
   842  }
   843  
   844  // ReadDurationDefault tries to parse the specified option as a duration. If there is
   845  // an error in parsing, the default option is returned.
   846  func (c *Config) ReadDurationDefault(id string, defaultValue time.Duration) time.Duration {
   847  	val, err := c.ReadDuration(id)
   848  	if err != nil {
   849  		return defaultValue
   850  	}
   851  	return val
   852  }
   853  
   854  // ReadStringListToMap tries to parse the specified option(s) as a comma separated list.
   855  // If there is an error in parsing, an empty list is returned.
   856  func (c *Config) ReadStringListToMap(keys ...string) map[string]struct{} {
   857  	val := c.ReadAlternativeDefault(keys, "")
   858  
   859  	return splitValue(val)
   860  }
   861  
   862  // ReadStringListToMapDefault tries to parse the specified option as a comma
   863  // separated list. If there is an error in parsing, an empty list is returned.
   864  func (c *Config) ReadStringListToMapDefault(key, defaultValue string) map[string]struct{} {
   865  	return c.ReadStringListAlternativeToMapDefault([]string{key}, defaultValue)
   866  }
   867  
   868  // ReadStringListAlternativeToMapDefault tries to parse the specified options as a comma sparated list.
   869  // If there is an error in parsing, an empty list is returned.
   870  func (c *Config) ReadStringListAlternativeToMapDefault(keys []string, defaultValue string) map[string]struct{} {
   871  	val := c.ReadAlternativeDefault(keys, defaultValue)
   872  
   873  	return splitValue(val)
   874  }
   875  
   876  // splitValue parses the value as a comma separated list.
   877  func splitValue(val string) map[string]struct{} {
   878  	list := make(map[string]struct{})
   879  	if val != "" {
   880  		for _, e := range strings.Split(val, ",") {
   881  			trimmed := strings.TrimSpace(e)
   882  			list[trimmed] = struct{}{}
   883  		}
   884  	}
   885  	return list
   886  }
   887  
   888  // NomadPluginConfig produces the NomadConfig struct which is sent to Nomad plugins
   889  func (c *Config) NomadPluginConfig() *base.AgentConfig {
   890  	return &base.AgentConfig{
   891  		Driver: &base.ClientDriverConfig{
   892  			ClientMinPort: c.ClientMinPort,
   893  			ClientMaxPort: c.ClientMaxPort,
   894  		},
   895  	}
   896  }