0a8e03f0f7
This PR exposes the following existing`consul-template` configuration options to Nomad jobspec authors in the `{job.group.task.template}` stanza. - `wait` It also exposes the following`consul-template` configuration to Nomad operators in the `{client.template}` stanza. - `max_stale` - `block_query_wait` - `consul_retry` - `vault_retry` - `wait` Finally, it adds the following new Nomad-specific configuration to the `{client.template}` stanza that allows Operators to set bounds on what `jobspec` authors configure. - `wait_bounds` Co-authored-by: Tim Gross <tgross@hashicorp.com> Co-authored-by: Michael Schurter <mschurter@hashicorp.com>
916 lines
28 KiB
Go
916 lines
28 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/consul-template/config"
|
|
"github.com/hashicorp/nomad/client/lib/cgutil"
|
|
"github.com/hashicorp/nomad/command/agent/host"
|
|
|
|
log "github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/nomad/client/state"
|
|
"github.com/hashicorp/nomad/helper"
|
|
"github.com/hashicorp/nomad/helper/pluginutils/loader"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
structsc "github.com/hashicorp/nomad/nomad/structs/config"
|
|
"github.com/hashicorp/nomad/plugins/base"
|
|
"github.com/hashicorp/nomad/version"
|
|
)
|
|
|
|
var (
|
|
// DefaultEnvDenylist is the default set of environment variables that are
|
|
// filtered when passing the environment variables of the host to a task.
|
|
DefaultEnvDenylist = strings.Join(host.DefaultEnvDenyList, ",")
|
|
|
|
// DefaultUserDenylist is the default set of users that tasks are not
|
|
// allowed to run as when using a driver in "user.checked_drivers"
|
|
DefaultUserDenylist = strings.Join([]string{
|
|
"root",
|
|
"Administrator",
|
|
}, ",")
|
|
|
|
// DefaultUserCheckedDrivers is the set of drivers we apply the user
|
|
// denylist onto. For virtualized drivers it often doesn't make sense to
|
|
// make this stipulation so by default they are ignored.
|
|
DefaultUserCheckedDrivers = strings.Join([]string{
|
|
"exec",
|
|
"qemu",
|
|
"java",
|
|
}, ",")
|
|
|
|
// A mapping of directories on the host OS to attempt to embed inside each
|
|
// task's chroot.
|
|
DefaultChrootEnv = map[string]string{
|
|
"/bin": "/bin",
|
|
"/etc": "/etc",
|
|
"/lib": "/lib",
|
|
"/lib32": "/lib32",
|
|
"/lib64": "/lib64",
|
|
"/run/resolvconf": "/run/resolvconf",
|
|
"/sbin": "/sbin",
|
|
"/usr": "/usr",
|
|
|
|
// embed systemd-resolved paths for systemd-resolved paths:
|
|
// /etc/resolv.conf is a symlink to /run/systemd/resolve/stub-resolv.conf in such systems.
|
|
// In non-systemd systems, this mount is a no-op and the path is ignored if not present.
|
|
"/run/systemd/resolve": "/run/systemd/resolve",
|
|
}
|
|
|
|
DefaultTemplateMaxStale = 5 * time.Second
|
|
)
|
|
|
|
// RPCHandler can be provided to the Client if there is a local server
|
|
// to avoid going over the network. If not provided, the Client will
|
|
// maintain a connection pool to the servers
|
|
type RPCHandler interface {
|
|
RPC(method string, args interface{}, reply interface{}) error
|
|
}
|
|
|
|
// Config is used to parameterize and configure the behavior of the client
|
|
type Config struct {
|
|
// DevMode controls if we are in a development mode which
|
|
// avoids persistent storage.
|
|
DevMode bool
|
|
|
|
// EnableDebug is used to enable debugging RPC endpoints
|
|
// in the absence of ACLs
|
|
EnableDebug bool
|
|
|
|
// StateDir is where we store our state
|
|
StateDir string
|
|
|
|
// AllocDir is where we store data for allocations
|
|
AllocDir string
|
|
|
|
// LogOutput is the destination for logs
|
|
LogOutput io.Writer
|
|
|
|
// Logger provides a logger to the client
|
|
Logger log.InterceptLogger
|
|
|
|
// Region is the clients region
|
|
Region string
|
|
|
|
// Network interface to be used in network fingerprinting
|
|
NetworkInterface string
|
|
|
|
// Network speed is the default speed of network interfaces if they can not
|
|
// be determined dynamically.
|
|
NetworkSpeed int
|
|
|
|
// CpuCompute is the default total CPU compute if they can not be determined
|
|
// dynamically. It should be given as Cores * MHz (2 Cores * 2 Ghz = 4000)
|
|
CpuCompute int
|
|
|
|
// MemoryMB is the default node total memory in megabytes if it cannot be
|
|
// determined dynamically.
|
|
MemoryMB int
|
|
|
|
// MaxKillTimeout allows capping the user-specifiable KillTimeout. If the
|
|
// task's KillTimeout is greater than the MaxKillTimeout, MaxKillTimeout is
|
|
// used.
|
|
MaxKillTimeout time.Duration
|
|
|
|
// Servers is a list of known server addresses. These are as "host:port"
|
|
Servers []string
|
|
|
|
// RPCHandler can be provided to avoid network traffic if the
|
|
// server is running locally.
|
|
RPCHandler RPCHandler
|
|
|
|
// Node provides the base node
|
|
Node *structs.Node
|
|
|
|
// ClientMaxPort is the upper range of the ports that the client uses for
|
|
// communicating with plugin subsystems over loopback
|
|
ClientMaxPort uint
|
|
|
|
// ClientMinPort is the lower range of the ports that the client uses for
|
|
// communicating with plugin subsystems over loopback
|
|
ClientMinPort uint
|
|
|
|
// MaxDynamicPort is the largest dynamic port generated
|
|
MaxDynamicPort int
|
|
|
|
// MinDynamicPort is the smallest dynamic port generated
|
|
MinDynamicPort int
|
|
|
|
// A mapping of directories on the host OS to attempt to embed inside each
|
|
// task's chroot.
|
|
ChrootEnv map[string]string
|
|
|
|
// Options provides arbitrary key-value configuration for nomad internals,
|
|
// like fingerprinters and drivers. The format is:
|
|
//
|
|
// namespace.option = value
|
|
Options map[string]string
|
|
|
|
// Version is the version of the Nomad client
|
|
Version *version.VersionInfo
|
|
|
|
// ConsulConfig is this Agent's Consul configuration
|
|
ConsulConfig *structsc.ConsulConfig
|
|
|
|
// VaultConfig is this Agent's Vault configuration
|
|
VaultConfig *structsc.VaultConfig
|
|
|
|
// StatsCollectionInterval is the interval at which the Nomad client
|
|
// collects resource usage stats
|
|
StatsCollectionInterval time.Duration
|
|
|
|
// PublishNodeMetrics determines whether nomad is going to publish node
|
|
// level metrics to remote Telemetry sinks
|
|
PublishNodeMetrics bool
|
|
|
|
// PublishAllocationMetrics determines whether nomad is going to publish
|
|
// allocation metrics to remote Telemetry sinks
|
|
PublishAllocationMetrics bool
|
|
|
|
// TLSConfig holds various TLS related configurations
|
|
TLSConfig *structsc.TLSConfig
|
|
|
|
// GCInterval is the time interval at which the client triggers garbage
|
|
// collection
|
|
GCInterval time.Duration
|
|
|
|
// GCParallelDestroys is the number of parallel destroys the garbage
|
|
// collector will allow.
|
|
GCParallelDestroys int
|
|
|
|
// GCDiskUsageThreshold is the disk usage threshold given as a percent
|
|
// beyond which the Nomad client triggers GC of terminal allocations
|
|
GCDiskUsageThreshold float64
|
|
|
|
// GCInodeUsageThreshold is the inode usage threshold given as a percent
|
|
// beyond which the Nomad client triggers GC of the terminal allocations
|
|
GCInodeUsageThreshold float64
|
|
|
|
// GCMaxAllocs is the maximum number of allocations a node can have
|
|
// before garbage collection is triggered.
|
|
GCMaxAllocs int
|
|
|
|
// LogLevel is the level of the logs to putout
|
|
LogLevel string
|
|
|
|
// NoHostUUID disables using the host's UUID and will force generation of a
|
|
// random UUID.
|
|
NoHostUUID bool
|
|
|
|
// ACLEnabled controls if ACL enforcement and management is enabled.
|
|
ACLEnabled bool
|
|
|
|
// ACLTokenTTL is how long we cache token values for
|
|
ACLTokenTTL time.Duration
|
|
|
|
// ACLPolicyTTL is how long we cache policy values for
|
|
ACLPolicyTTL time.Duration
|
|
|
|
// DisableRemoteExec disables remote exec targeting tasks on this client
|
|
DisableRemoteExec bool
|
|
|
|
// TemplateConfig includes configuration for template rendering
|
|
TemplateConfig *ClientTemplateConfig
|
|
|
|
// RPCHoldTimeout is how long an RPC can be "held" before it is errored.
|
|
// This is used to paper over a loss of leadership by instead holding RPCs,
|
|
// so that the caller experiences a slow response rather than an error.
|
|
// This period is meant to be long enough for a leader election to take
|
|
// place, and a small jitter is applied to avoid a thundering herd.
|
|
RPCHoldTimeout time.Duration
|
|
|
|
// PluginLoader is used to load plugins.
|
|
PluginLoader loader.PluginCatalog
|
|
|
|
// PluginSingletonLoader is a plugin loader that will returns singleton
|
|
// instances of the plugins.
|
|
PluginSingletonLoader loader.PluginCatalog
|
|
|
|
// StateDBFactory is used to override stateDB implementations,
|
|
StateDBFactory state.NewStateDBFunc
|
|
|
|
// CNIPath is the path used to search for CNI plugins. Multiple paths can
|
|
// be specified with colon delimited
|
|
CNIPath string
|
|
|
|
// CNIConfigDir is the directory where CNI network configuration is located. The
|
|
// client will use this path when fingerprinting CNI networks.
|
|
CNIConfigDir string
|
|
|
|
// CNIInterfacePrefix is the prefix to use when creating CNI network interfaces. This
|
|
// defaults to 'eth', therefore the first interface created by CNI inside the alloc
|
|
// network will be 'eth0'.
|
|
CNIInterfacePrefix string
|
|
|
|
// BridgeNetworkName is the name to use for the bridge created in bridge
|
|
// networking mode. This defaults to 'nomad' if not set
|
|
BridgeNetworkName string
|
|
|
|
// BridgeNetworkAllocSubnet is the IP subnet to use for address allocation
|
|
// for allocations in bridge networking mode. Subnet must be in CIDR
|
|
// notation
|
|
BridgeNetworkAllocSubnet string
|
|
|
|
// HostVolumes is a map of the configured host volumes by name.
|
|
HostVolumes map[string]*structs.ClientHostVolumeConfig
|
|
|
|
// HostNetworks is a map of the conigured host networks by name.
|
|
HostNetworks map[string]*structs.ClientHostNetworkConfig
|
|
|
|
// BindWildcardDefaultHostNetwork toggles if the default host network should accept all
|
|
// destinations (true) or only filter on the IP of the default host network (false) when
|
|
// port mapping. This allows Nomad clients with no defined host networks to accept and
|
|
// port forward traffic only matching on the destination port. An example use of this
|
|
// is when a network loadbalancer is utilizing direct server return and the destination
|
|
// address of incomming packets does not match the IP address of the host interface.
|
|
//
|
|
// This configuration is only considered if no host networks are defined.
|
|
BindWildcardDefaultHostNetwork bool
|
|
|
|
// CgroupParent is the parent cgroup Nomad should use when managing any cgroup subsystems.
|
|
// Currently this only includes the 'cpuset' cgroup subsystem.
|
|
CgroupParent string
|
|
|
|
// ReservableCores if set overrides the set of reservable cores reported in fingerprinting.
|
|
ReservableCores []uint16
|
|
}
|
|
|
|
// ClientTemplateConfig is configuration on the client specific to template
|
|
// rendering
|
|
type ClientTemplateConfig struct {
|
|
// FunctionDenylist disables functions in consul-template that
|
|
// are unsafe because they expose information from the client host.
|
|
FunctionDenylist []string `hcl:"function_denylist"`
|
|
|
|
// Deprecated: COMPAT(1.0) consul-template uses inclusive language from
|
|
// v0.25.0 - function_blacklist is kept for compatibility
|
|
FunctionBlacklist []string `hcl:"function_blacklist"`
|
|
|
|
// DisableSandbox allows templates to access arbitrary files on the
|
|
// client host. By default templates can access files only within
|
|
// the task directory.
|
|
DisableSandbox bool `hcl:"disable_file_sandbox"`
|
|
|
|
// This is the maximum interval to allow "stale" data. By default, only the
|
|
// Consul leader will respond to queries; any requests to a follower will
|
|
// forward to the leader. In large clusters with many requests, this is not as
|
|
// scalable, so this option allows any follower to respond to a query, so long
|
|
// as the last-replicated data is within these bounds. Higher values result in
|
|
// less cluster load, but are more likely to have outdated data.
|
|
// NOTE: Since Consul Template uses a pointer, this field uses a pointer which
|
|
// is inconsistent with how Nomad typically works. This decision was made to
|
|
// maintain parity with the external subsystem, not to establish a new standard.
|
|
MaxStale *time.Duration `hcl:"-"`
|
|
MaxStaleHCL string `hcl:"max_stale,optional"`
|
|
|
|
// BlockQueryWaitTime is amount of time in seconds to do a blocking query for.
|
|
// Many endpoints in Consul support a feature known as "blocking queries".
|
|
// A blocking query is used to wait for a potential change using long polling.
|
|
// NOTE: Since Consul Template uses a pointer, this field uses a pointer which
|
|
// is inconsistent with how Nomad typically works. This decision was made to
|
|
// maintain parity with the external subsystem, not to establish a new standard.
|
|
BlockQueryWaitTime *time.Duration `hcl:"-"`
|
|
BlockQueryWaitTimeHCL string `hcl:"block_query_wait,optional"`
|
|
|
|
// Wait is the quiescence timers; it defines the minimum and maximum amount of
|
|
// time to wait for the Consul cluster to reach a consistent state before rendering a
|
|
// template. This is useful to enable in systems where Consul is experiencing
|
|
// a lot of flapping because it will reduce the number of times a template is rendered.
|
|
Wait *WaitConfig `hcl:"wait,optional" json:"-"`
|
|
|
|
// WaitBounds allows operators to define boundaries on individual template wait
|
|
// configuration overrides. If set, this ensures that if a job author specifies
|
|
// a wait configuration with values the cluster operator does not allow, the
|
|
// cluster operator's boundary will be applied rather than the job author's
|
|
// out of bounds configuration.
|
|
WaitBounds *WaitConfig `hcl:"wait_bounds,optional" json:"-"`
|
|
|
|
// This controls the retry behavior when an error is returned from Consul.
|
|
// Consul Template is highly fault tolerant, meaning it does not exit in the
|
|
// face of failure. Instead, it uses exponential back-off and retry functions
|
|
// to wait for the cluster to become available, as is customary in distributed
|
|
// systems.
|
|
ConsulRetry *RetryConfig `hcl:"consul_retry,optional"`
|
|
|
|
// This controls the retry behavior when an error is returned from Vault.
|
|
// Consul Template is highly fault tolerant, meaning it does not exit in the
|
|
// face of failure. Instead, it uses exponential back-off and retry functions
|
|
// to wait for the cluster to become available, as is customary in distributed
|
|
// systems.
|
|
VaultRetry *RetryConfig `hcl:"vault_retry,optional"`
|
|
}
|
|
|
|
// Copy returns a deep copy of a ClientTemplateConfig
|
|
func (c *ClientTemplateConfig) Copy() *ClientTemplateConfig {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
nc := new(ClientTemplateConfig)
|
|
*nc = *c
|
|
nc.FunctionDenylist = helper.CopySliceString(nc.FunctionDenylist)
|
|
|
|
if c.BlockQueryWaitTime != nil {
|
|
nc.BlockQueryWaitTime = &*c.BlockQueryWaitTime
|
|
}
|
|
|
|
if c.MaxStale != nil {
|
|
nc.MaxStale = &*c.MaxStale
|
|
}
|
|
|
|
if c.Wait != nil {
|
|
nc.Wait = c.Wait.Copy()
|
|
}
|
|
|
|
if c.ConsulRetry != nil {
|
|
nc.ConsulRetry = c.ConsulRetry.Copy()
|
|
}
|
|
|
|
if c.VaultRetry != nil {
|
|
nc.VaultRetry = c.VaultRetry.Copy()
|
|
}
|
|
|
|
return nc
|
|
}
|
|
|
|
// Merge merges the values of two ClientTemplateConfigs. If first copies the receiver
|
|
// instance, and then overrides those values with the instance to merge with.
|
|
func (c *ClientTemplateConfig) Merge(b *ClientTemplateConfig) *ClientTemplateConfig {
|
|
if c == nil {
|
|
return b
|
|
}
|
|
|
|
result := *c
|
|
|
|
if b == nil {
|
|
return &result
|
|
}
|
|
|
|
if b.BlockQueryWaitTime != nil {
|
|
result.BlockQueryWaitTime = b.BlockQueryWaitTime
|
|
}
|
|
if b.BlockQueryWaitTimeHCL != "" {
|
|
result.BlockQueryWaitTimeHCL = b.BlockQueryWaitTimeHCL
|
|
}
|
|
|
|
if b.ConsulRetry != nil {
|
|
result.ConsulRetry = result.ConsulRetry.Merge(b.ConsulRetry)
|
|
}
|
|
|
|
result.DisableSandbox = b.DisableSandbox
|
|
|
|
// Maintain backward compatibility for older clients
|
|
if len(b.FunctionBlacklist) > 0 {
|
|
for _, fn := range b.FunctionBlacklist {
|
|
if !helper.SliceStringContains(result.FunctionBlacklist, fn) {
|
|
result.FunctionBlacklist = append(result.FunctionBlacklist, fn)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(b.FunctionDenylist) > 0 {
|
|
for _, fn := range b.FunctionDenylist {
|
|
if !helper.SliceStringContains(result.FunctionDenylist, fn) {
|
|
result.FunctionDenylist = append(result.FunctionDenylist, fn)
|
|
}
|
|
}
|
|
}
|
|
|
|
if b.MaxStale != nil {
|
|
result.MaxStale = b.MaxStale
|
|
}
|
|
|
|
if b.MaxStaleHCL != "" {
|
|
result.MaxStaleHCL = b.MaxStaleHCL
|
|
}
|
|
|
|
if b.Wait != nil {
|
|
result.Wait = result.Wait.Merge(b.Wait)
|
|
}
|
|
|
|
if b.WaitBounds != nil {
|
|
result.WaitBounds = result.WaitBounds.Merge(b.WaitBounds)
|
|
}
|
|
|
|
if b.VaultRetry != nil {
|
|
result.VaultRetry = result.VaultRetry.Merge(b.VaultRetry)
|
|
}
|
|
|
|
return &result
|
|
}
|
|
|
|
func (c *ClientTemplateConfig) IsEmpty() bool {
|
|
if c == nil {
|
|
return true
|
|
}
|
|
|
|
return c.BlockQueryWaitTime == nil &&
|
|
c.BlockQueryWaitTimeHCL == "" &&
|
|
c.MaxStale == nil &&
|
|
c.MaxStaleHCL == "" &&
|
|
c.Wait.IsEmpty() &&
|
|
c.ConsulRetry.IsEmpty() &&
|
|
c.VaultRetry.IsEmpty()
|
|
}
|
|
|
|
// WaitConfig is mirrored from templateconfig.WaitConfig because we need to handle
|
|
// the HCL conversion which happens in agent.ParseConfigFile
|
|
// NOTE: Since Consul Template requires pointers, this type uses pointers to fields
|
|
// which is inconsistent with how Nomad typically works. This decision was made
|
|
// to maintain parity with the external subsystem, not to establish a new standard.
|
|
type WaitConfig struct {
|
|
Min *time.Duration `hcl:"-"`
|
|
MinHCL string `hcl:"min,optional" json:"-"`
|
|
Max *time.Duration `hcl:"-"`
|
|
MaxHCL string `hcl:"max,optional" json:"-"`
|
|
}
|
|
|
|
// Copy returns a deep copy of the receiver.
|
|
func (wc *WaitConfig) Copy() *WaitConfig {
|
|
if wc == nil {
|
|
return nil
|
|
}
|
|
|
|
nwc := new(WaitConfig)
|
|
|
|
if wc.Min != nil {
|
|
nwc.Min = &*wc.Min
|
|
}
|
|
|
|
if wc.Max != nil {
|
|
nwc.Max = &*wc.Max
|
|
}
|
|
|
|
return wc
|
|
}
|
|
|
|
// Equals returns the result of reflect.DeepEqual
|
|
func (wc *WaitConfig) Equals(other *WaitConfig) bool {
|
|
return reflect.DeepEqual(wc, other)
|
|
}
|
|
|
|
// IsEmpty returns true if the receiver only contains an instance with no fields set.
|
|
func (wc *WaitConfig) IsEmpty() bool {
|
|
if wc == nil {
|
|
return true
|
|
}
|
|
return wc.Equals(&WaitConfig{})
|
|
}
|
|
|
|
// Validate returns an error if the receiver is nil or empty or if Min is greater
|
|
// than Max the user specified Max.
|
|
func (wc *WaitConfig) Validate() error {
|
|
// If the config is nil or empty return false so that it is never assigned.
|
|
if wc == nil || wc.IsEmpty() {
|
|
return errors.New("wait config is nil or empty")
|
|
}
|
|
|
|
// If min is nil, return
|
|
if wc.Min == nil {
|
|
return nil
|
|
}
|
|
|
|
// If min isn't nil, make sure Max is less than Min.
|
|
if wc.Max != nil {
|
|
if *wc.Min > *wc.Max {
|
|
return fmt.Errorf("wait config min %d is greater than max %d", *wc.Min, *wc.Max)
|
|
}
|
|
}
|
|
|
|
// Otherwise, return nil. Consul Template will set a Max based off of Min.
|
|
return nil
|
|
}
|
|
|
|
// Merge merges two WaitConfigs. The passed instance always takes precedence.
|
|
func (wc *WaitConfig) Merge(b *WaitConfig) *WaitConfig {
|
|
if wc == nil {
|
|
return b
|
|
}
|
|
|
|
result := *wc
|
|
if b == nil {
|
|
return &result
|
|
}
|
|
|
|
if b.Min != nil {
|
|
result.Min = &*b.Min
|
|
}
|
|
|
|
if b.MinHCL != "" {
|
|
result.MinHCL = b.MinHCL
|
|
}
|
|
|
|
if b.Max != nil {
|
|
result.Max = &*b.Max
|
|
}
|
|
|
|
if b.MaxHCL != "" {
|
|
result.MaxHCL = b.MaxHCL
|
|
}
|
|
|
|
return &result
|
|
}
|
|
|
|
// ToConsulTemplate converts a client WaitConfig instance to a consul-template WaitConfig
|
|
func (wc *WaitConfig) ToConsulTemplate() (*config.WaitConfig, error) {
|
|
if wc.IsEmpty() {
|
|
return nil, errors.New("wait config is empty")
|
|
}
|
|
|
|
if err := wc.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := &config.WaitConfig{Enabled: helper.BoolToPtr(true)}
|
|
|
|
if wc.Min != nil {
|
|
result.Min = wc.Min
|
|
}
|
|
|
|
if wc.Max != nil {
|
|
result.Max = wc.Max
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// RetryConfig is mirrored from templateconfig.WaitConfig because we need to handle
|
|
// the HCL indirection to support mapping in agent.ParseConfigFile.
|
|
// NOTE: Since Consul Template requires pointers, this type uses pointers to fields
|
|
// which is inconsistent with how Nomad typically works. However, since zero in
|
|
// Attempts and MaxBackoff have special meaning, it is necessary to know if the
|
|
// value was actually set rather than if it defaulted to 0. The rest of the fields
|
|
// use pointers to maintain parity with the external subystem, not to establish
|
|
// a new standard.
|
|
type RetryConfig struct {
|
|
// Attempts is the total number of maximum attempts to retry before letting
|
|
// the error fall through.
|
|
// 0 means unlimited.
|
|
Attempts *int `hcl:"attempts,optional"`
|
|
// Backoff is the base of the exponential backoff. This number will be
|
|
// multiplied by the next power of 2 on each iteration.
|
|
Backoff *time.Duration `hcl:"-"`
|
|
BackoffHCL string `hcl:"backoff,optional" json:"-"`
|
|
// MaxBackoff is an upper limit to the sleep time between retries
|
|
// A MaxBackoff of 0 means there is no limit to the exponential growth of the backoff.
|
|
MaxBackoff *time.Duration `hcl:"-"`
|
|
MaxBackoffHCL string `hcl:"max_backoff,optional" json:"-"`
|
|
}
|
|
|
|
func (rc *RetryConfig) Copy() *RetryConfig {
|
|
if rc == nil {
|
|
return nil
|
|
}
|
|
|
|
nrc := new(RetryConfig)
|
|
*nrc = *rc
|
|
|
|
// Now copy pointer values
|
|
if rc.Attempts != nil {
|
|
nrc.Attempts = &*rc.Attempts
|
|
}
|
|
if rc.Backoff != nil {
|
|
nrc.Backoff = &*rc.Backoff
|
|
}
|
|
if rc.MaxBackoff != nil {
|
|
nrc.MaxBackoff = &*rc.MaxBackoff
|
|
}
|
|
|
|
return nrc
|
|
}
|
|
|
|
// Equals returns the result of reflect.DeepEqual
|
|
func (rc *RetryConfig) Equals(other *RetryConfig) bool {
|
|
return reflect.DeepEqual(rc, other)
|
|
}
|
|
|
|
// IsEmpty returns true if the receiver only contains an instance with no fields set.
|
|
func (rc *RetryConfig) IsEmpty() bool {
|
|
if rc == nil {
|
|
return true
|
|
}
|
|
|
|
return rc.Equals(&RetryConfig{})
|
|
}
|
|
|
|
// Validate returns an error if the receiver is nil or empty, or if Backoff
|
|
// is greater than MaxBackoff.
|
|
func (rc *RetryConfig) Validate() error {
|
|
// If the config is nil or empty return false so that it is never assigned.
|
|
if rc == nil || rc.IsEmpty() {
|
|
return errors.New("retry config is nil or empty")
|
|
}
|
|
|
|
// If Backoff not set, no need to validate
|
|
if rc.Backoff == nil {
|
|
return nil
|
|
}
|
|
|
|
// MaxBackoff nil will end up defaulted to 1 minutes. We should validate that
|
|
// the user supplied backoff does not exceed that.
|
|
if rc.MaxBackoff == nil && *rc.Backoff > config.DefaultRetryMaxBackoff {
|
|
return fmt.Errorf("retry config backoff %d is greater than default max_backoff %d", *rc.Backoff, config.DefaultRetryMaxBackoff)
|
|
}
|
|
|
|
// MaxBackoff == 0 means backoff is unbounded. No need to validate.
|
|
if rc.MaxBackoff != nil && *rc.MaxBackoff == 0 {
|
|
return nil
|
|
}
|
|
|
|
if rc.MaxBackoff != nil && *rc.Backoff > *rc.MaxBackoff {
|
|
return fmt.Errorf("retry config backoff %d is greater than max_backoff %d", *rc.Backoff, *rc.MaxBackoff)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Merge merges two RetryConfigs. The passed instance always takes precedence.
|
|
func (rc *RetryConfig) Merge(b *RetryConfig) *RetryConfig {
|
|
if rc == nil {
|
|
return b
|
|
}
|
|
|
|
result := *rc
|
|
if b == nil {
|
|
return &result
|
|
}
|
|
|
|
if b.Attempts != nil {
|
|
result.Attempts = &*b.Attempts
|
|
}
|
|
|
|
if b.Backoff != nil {
|
|
result.Backoff = &*b.Backoff
|
|
}
|
|
|
|
if b.BackoffHCL != "" {
|
|
result.BackoffHCL = b.BackoffHCL
|
|
}
|
|
|
|
if b.MaxBackoff != nil {
|
|
result.MaxBackoff = &*b.MaxBackoff
|
|
}
|
|
|
|
if b.MaxBackoffHCL != "" {
|
|
result.MaxBackoffHCL = b.MaxBackoffHCL
|
|
}
|
|
|
|
return &result
|
|
}
|
|
|
|
// ToConsulTemplate converts a client RetryConfig instance to a consul-template RetryConfig
|
|
func (rc *RetryConfig) ToConsulTemplate() (*config.RetryConfig, error) {
|
|
if err := rc.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := &config.RetryConfig{Enabled: helper.BoolToPtr(true)}
|
|
|
|
if rc.Attempts != nil {
|
|
result.Attempts = rc.Attempts
|
|
}
|
|
|
|
if rc.Backoff != nil {
|
|
result.Backoff = rc.Backoff
|
|
}
|
|
|
|
if rc.MaxBackoff != nil {
|
|
result.MaxBackoff = &*rc.MaxBackoff
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (c *Config) Copy() *Config {
|
|
nc := new(Config)
|
|
*nc = *c
|
|
nc.Node = nc.Node.Copy()
|
|
nc.Servers = helper.CopySliceString(nc.Servers)
|
|
nc.Options = helper.CopyMapStringString(nc.Options)
|
|
nc.HostVolumes = structs.CopyMapStringClientHostVolumeConfig(nc.HostVolumes)
|
|
nc.ConsulConfig = c.ConsulConfig.Copy()
|
|
nc.VaultConfig = c.VaultConfig.Copy()
|
|
nc.TemplateConfig = c.TemplateConfig.Copy()
|
|
if c.ReservableCores != nil {
|
|
nc.ReservableCores = make([]uint16, len(c.ReservableCores))
|
|
copy(nc.ReservableCores, c.ReservableCores)
|
|
}
|
|
return nc
|
|
}
|
|
|
|
// DefaultConfig returns the default configuration
|
|
func DefaultConfig() *Config {
|
|
return &Config{
|
|
Version: version.GetVersion(),
|
|
VaultConfig: structsc.DefaultVaultConfig(),
|
|
ConsulConfig: structsc.DefaultConsulConfig(),
|
|
LogOutput: os.Stderr,
|
|
Region: "global",
|
|
StatsCollectionInterval: 1 * time.Second,
|
|
TLSConfig: &structsc.TLSConfig{},
|
|
LogLevel: "DEBUG",
|
|
GCInterval: 1 * time.Minute,
|
|
GCParallelDestroys: 2,
|
|
GCDiskUsageThreshold: 80,
|
|
GCInodeUsageThreshold: 70,
|
|
GCMaxAllocs: 50,
|
|
NoHostUUID: true,
|
|
DisableRemoteExec: false,
|
|
TemplateConfig: &ClientTemplateConfig{
|
|
FunctionDenylist: []string{"plugin"},
|
|
DisableSandbox: false,
|
|
},
|
|
RPCHoldTimeout: 5 * time.Second,
|
|
CNIPath: "/opt/cni/bin",
|
|
CNIConfigDir: "/opt/cni/config",
|
|
CNIInterfacePrefix: "eth",
|
|
HostNetworks: map[string]*structs.ClientHostNetworkConfig{},
|
|
CgroupParent: cgutil.DefaultCgroupParent,
|
|
MaxDynamicPort: structs.DefaultMinDynamicPort,
|
|
MinDynamicPort: structs.DefaultMaxDynamicPort,
|
|
}
|
|
}
|
|
|
|
// Read returns the specified configuration value or "".
|
|
func (c *Config) Read(id string) string {
|
|
return c.Options[id]
|
|
}
|
|
|
|
// ReadDefault returns the specified configuration value, or the specified
|
|
// default value if none is set.
|
|
func (c *Config) ReadDefault(id string, defaultValue string) string {
|
|
return c.ReadAlternativeDefault([]string{id}, defaultValue)
|
|
}
|
|
|
|
// ReadAlternativeDefault returns the specified configuration value, or the
|
|
// specified value if none is set.
|
|
func (c *Config) ReadAlternativeDefault(ids []string, defaultValue string) string {
|
|
for _, id := range ids {
|
|
val, ok := c.Options[id]
|
|
if ok {
|
|
return val
|
|
}
|
|
}
|
|
|
|
return defaultValue
|
|
}
|
|
|
|
// ReadBool parses the specified option as a boolean.
|
|
func (c *Config) ReadBool(id string) (bool, error) {
|
|
val, ok := c.Options[id]
|
|
if !ok {
|
|
return false, fmt.Errorf("Specified config is missing from options")
|
|
}
|
|
bval, err := strconv.ParseBool(val)
|
|
if err != nil {
|
|
return false, fmt.Errorf("Failed to parse %s as bool: %s", val, err)
|
|
}
|
|
return bval, nil
|
|
}
|
|
|
|
// ReadBoolDefault tries to parse the specified option as a boolean. If there is
|
|
// an error in parsing, the default option is returned.
|
|
func (c *Config) ReadBoolDefault(id string, defaultValue bool) bool {
|
|
val, err := c.ReadBool(id)
|
|
if err != nil {
|
|
return defaultValue
|
|
}
|
|
return val
|
|
}
|
|
|
|
// ReadInt parses the specified option as a int.
|
|
func (c *Config) ReadInt(id string) (int, error) {
|
|
val, ok := c.Options[id]
|
|
if !ok {
|
|
return 0, fmt.Errorf("Specified config is missing from options")
|
|
}
|
|
ival, err := strconv.Atoi(val)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("Failed to parse %s as int: %s", val, err)
|
|
}
|
|
return ival, nil
|
|
}
|
|
|
|
// ReadIntDefault tries to parse the specified option as a int. If there is
|
|
// an error in parsing, the default option is returned.
|
|
func (c *Config) ReadIntDefault(id string, defaultValue int) int {
|
|
val, err := c.ReadInt(id)
|
|
if err != nil {
|
|
return defaultValue
|
|
}
|
|
return val
|
|
}
|
|
|
|
// ReadDuration parses the specified option as a duration.
|
|
func (c *Config) ReadDuration(id string) (time.Duration, error) {
|
|
val, ok := c.Options[id]
|
|
if !ok {
|
|
return time.Duration(0), fmt.Errorf("Specified config is missing from options")
|
|
}
|
|
dval, err := time.ParseDuration(val)
|
|
if err != nil {
|
|
return time.Duration(0), fmt.Errorf("Failed to parse %s as time duration: %s", val, err)
|
|
}
|
|
return dval, nil
|
|
}
|
|
|
|
// ReadDurationDefault tries to parse the specified option as a duration. If there is
|
|
// an error in parsing, the default option is returned.
|
|
func (c *Config) ReadDurationDefault(id string, defaultValue time.Duration) time.Duration {
|
|
val, err := c.ReadDuration(id)
|
|
if err != nil {
|
|
return defaultValue
|
|
}
|
|
return val
|
|
}
|
|
|
|
// ReadStringListToMap tries to parse the specified option(s) as a comma separated list.
|
|
// If there is an error in parsing, an empty list is returned.
|
|
func (c *Config) ReadStringListToMap(keys ...string) map[string]struct{} {
|
|
val := c.ReadAlternativeDefault(keys, "")
|
|
|
|
return splitValue(val)
|
|
}
|
|
|
|
// ReadStringListToMapDefault tries to parse the specified option as a comma
|
|
// separated list. If there is an error in parsing, an empty list is returned.
|
|
func (c *Config) ReadStringListToMapDefault(key, defaultValue string) map[string]struct{} {
|
|
return c.ReadStringListAlternativeToMapDefault([]string{key}, defaultValue)
|
|
}
|
|
|
|
// ReadStringListAlternativeToMapDefault tries to parse the specified options as a comma sparated list.
|
|
// If there is an error in parsing, an empty list is returned.
|
|
func (c *Config) ReadStringListAlternativeToMapDefault(keys []string, defaultValue string) map[string]struct{} {
|
|
val := c.ReadAlternativeDefault(keys, defaultValue)
|
|
|
|
return splitValue(val)
|
|
}
|
|
|
|
// splitValue parses the value as a comma separated list.
|
|
func splitValue(val string) map[string]struct{} {
|
|
list := make(map[string]struct{})
|
|
if val != "" {
|
|
for _, e := range strings.Split(val, ",") {
|
|
trimmed := strings.TrimSpace(e)
|
|
list[trimmed] = struct{}{}
|
|
}
|
|
}
|
|
return list
|
|
}
|
|
|
|
// NomadPluginConfig produces the NomadConfig struct which is sent to Nomad plugins
|
|
func (c *Config) NomadPluginConfig() *base.AgentConfig {
|
|
return &base.AgentConfig{
|
|
Driver: &base.ClientDriverConfig{
|
|
ClientMinPort: c.ClientMinPort,
|
|
ClientMaxPort: c.ClientMaxPort,
|
|
},
|
|
}
|
|
}
|