package api import ( "fmt" "time" ) // CheckRestart describes if and when a task should be restarted based on // failing health checks. type CheckRestart struct { Limit int `mapstructure:"limit"` Grace *time.Duration `mapstructure:"grace"` IgnoreWarnings bool `mapstructure:"ignore_warnings"` } // Canonicalize CheckRestart fields if not nil. func (c *CheckRestart) Canonicalize() { if c == nil { return } if c.Grace == nil { c.Grace = timeToPtr(1 * time.Second) } } // Copy returns a copy of CheckRestart or nil if unset. func (c *CheckRestart) Copy() *CheckRestart { if c == nil { return nil } nc := new(CheckRestart) nc.Limit = c.Limit if c.Grace != nil { g := *c.Grace nc.Grace = &g } nc.IgnoreWarnings = c.IgnoreWarnings return nc } // Merge values from other CheckRestart over default values on this // CheckRestart and return merged copy. func (c *CheckRestart) Merge(o *CheckRestart) *CheckRestart { if c == nil { // Just return other return o } nc := c.Copy() if o == nil { // Nothing to merge return nc } if o.Limit > 0 { nc.Limit = o.Limit } if o.Grace != nil { nc.Grace = o.Grace } if o.IgnoreWarnings { nc.IgnoreWarnings = o.IgnoreWarnings } return nc } // ServiceCheck represents the consul health check that Nomad registers. type ServiceCheck struct { //FIXME Id is unused. Remove? Id string Name string Type string Command string Args []string Path string Protocol string PortLabel string `mapstructure:"port"` Expose bool AddressMode string `mapstructure:"address_mode"` Interval time.Duration Timeout time.Duration InitialStatus string `mapstructure:"initial_status"` TLSSkipVerify bool `mapstructure:"tls_skip_verify"` Header map[string][]string Method string CheckRestart *CheckRestart `mapstructure:"check_restart"` GRPCService string `mapstructure:"grpc_service"` GRPCUseTLS bool `mapstructure:"grpc_use_tls"` TaskName string `mapstructure:"task"` SuccessBeforePassing int `mapstructure:"success_before_passing"` FailuresBeforeCritical int `mapstructure:"failures_before_critical"` } // Service represents a Consul service definition. type Service struct { //FIXME Id is unused. Remove? Id string Name string Tags []string CanaryTags []string `mapstructure:"canary_tags"` EnableTagOverride bool `mapstructure:"enable_tag_override"` PortLabel string `mapstructure:"port"` AddressMode string `mapstructure:"address_mode"` Checks []ServiceCheck CheckRestart *CheckRestart `mapstructure:"check_restart"` Connect *ConsulConnect Meta map[string]string CanaryMeta map[string]string TaskName string `mapstructure:"task"` } // Canonicalize the Service by ensuring its name and address mode are set. Task // will be nil for group services. func (s *Service) Canonicalize(t *Task, tg *TaskGroup, job *Job) { if s.Name == "" { if t != nil { s.Name = fmt.Sprintf("%s-%s-%s", *job.Name, *tg.Name, t.Name) } else { s.Name = fmt.Sprintf("%s-%s", *job.Name, *tg.Name) } } // Default to AddressModeAuto if s.AddressMode == "" { s.AddressMode = "auto" } s.Connect.Canonicalize() // Canonicalize CheckRestart on Checks and merge Service.CheckRestart // into each check. for i, check := range s.Checks { s.Checks[i].CheckRestart = s.CheckRestart.Merge(check.CheckRestart) s.Checks[i].CheckRestart.Canonicalize() if s.Checks[i].SuccessBeforePassing < 0 { s.Checks[i].SuccessBeforePassing = 0 } if s.Checks[i].FailuresBeforeCritical < 0 { s.Checks[i].FailuresBeforeCritical = 0 } } } // ConsulConnect represents a Consul Connect jobspec stanza. type ConsulConnect struct { Native bool Gateway *ConsulGateway SidecarService *ConsulSidecarService `mapstructure:"sidecar_service"` SidecarTask *SidecarTask `mapstructure:"sidecar_task"` } func (cc *ConsulConnect) Canonicalize() { if cc == nil { return } cc.SidecarService.Canonicalize() cc.SidecarTask.Canonicalize() cc.Gateway.Canonicalize() } // ConsulSidecarService represents a Consul Connect SidecarService jobspec // stanza. type ConsulSidecarService struct { Tags []string Port string Proxy *ConsulProxy } func (css *ConsulSidecarService) Canonicalize() { if css == nil { return } if len(css.Tags) == 0 { css.Tags = nil } css.Proxy.Canonicalize() } // SidecarTask represents a subset of Task fields that can be set to override // the fields of the Task generated for the sidecar type SidecarTask struct { Name string Driver string User string Config map[string]interface{} Env map[string]string Resources *Resources Meta map[string]string KillTimeout *time.Duration `mapstructure:"kill_timeout"` LogConfig *LogConfig `mapstructure:"logs"` ShutdownDelay *time.Duration `mapstructure:"shutdown_delay"` KillSignal string `mapstructure:"kill_signal"` } func (st *SidecarTask) Canonicalize() { if st == nil { return } if len(st.Config) == 0 { st.Config = nil } if len(st.Env) == 0 { st.Env = nil } if st.Resources == nil { st.Resources = DefaultResources() } else { st.Resources.Canonicalize() } if st.LogConfig == nil { st.LogConfig = DefaultLogConfig() } else { st.LogConfig.Canonicalize() } if len(st.Meta) == 0 { st.Meta = nil } if st.KillTimeout == nil { st.KillTimeout = timeToPtr(5 * time.Second) } if st.ShutdownDelay == nil { st.ShutdownDelay = timeToPtr(0) } } // ConsulProxy represents a Consul Connect sidecar proxy jobspec stanza. type ConsulProxy struct { LocalServiceAddress string `mapstructure:"local_service_address"` LocalServicePort int `mapstructure:"local_service_port"` ExposeConfig *ConsulExposeConfig `mapstructure:"expose"` Upstreams []*ConsulUpstream Config map[string]interface{} } func (cp *ConsulProxy) Canonicalize() { if cp == nil { return } cp.ExposeConfig.Canonicalize() if len(cp.Upstreams) == 0 { cp.Upstreams = nil } if len(cp.Config) == 0 { cp.Config = nil } } // ConsulUpstream represents a Consul Connect upstream jobspec stanza. type ConsulUpstream struct { DestinationName string `mapstructure:"destination_name"` LocalBindPort int `mapstructure:"local_bind_port"` } type ConsulExposeConfig struct { Path []*ConsulExposePath `mapstructure:"path"` } func (cec *ConsulExposeConfig) Canonicalize() { if cec == nil { return } if len(cec.Path) == 0 { cec.Path = nil } } type ConsulExposePath struct { Path string Protocol string LocalPathPort int `mapstructure:"local_path_port"` ListenerPort string `mapstructure:"listener_port"` } // ConsulGateway is used to configure one of the Consul Connect Gateway types. type ConsulGateway struct { // Proxy is used to configure the Envoy instance acting as the gateway. Proxy *ConsulGatewayProxy // Ingress represents the Consul Configuration Entry for an Ingress Gateway. Ingress *ConsulIngressConfigEntry // Terminating is not yet supported. // Terminating *ConsulTerminatingConfigEntry // Mesh is not yet supported. // Mesh *ConsulMeshConfigEntry } func (g *ConsulGateway) Canonicalize() { if g == nil { return } g.Proxy.Canonicalize() g.Ingress.Canonicalize() } func (g *ConsulGateway) Copy() *ConsulGateway { if g == nil { return nil } return &ConsulGateway{ Proxy: g.Proxy.Copy(), Ingress: g.Ingress.Copy(), } } type ConsulGatewayBindAddress struct { Address string `mapstructure:"address"` Port int `mapstructure:"port"` } const ( defaultDNSDiscoveryType = "LOGICAL_DNS" ) var ( defaultGatewayConnectTimeout = 5 * time.Second ) // ConsulGatewayProxy is used to tune parameters of the proxy instance acting as // one of the forms of Connect gateways that Consul supports. // // https://www.consul.io/docs/connect/proxies/envoy#gateway-options type ConsulGatewayProxy struct { ConnectTimeout *time.Duration `mapstructure:"connect_timeout"` EnvoyGatewayBindTaggedAddresses bool `mapstructure:"envoy_gateway_bind_tagged_addresses"` EnvoyGatewayBindAddresses map[string]*ConsulGatewayBindAddress `mapstructure:"envoy_gateway_bind_addresses"` EnvoyGatewayNoDefaultBind bool `mapstructure:"envoy_gateway_no_default_bind"` EnvoyDNSDiscoveryType string `mapstructure:"envoy_dns_discovery_type"` Config map[string]interface{} // escape hatch envoy config } func (p *ConsulGatewayProxy) Canonicalize() { if p == nil { return } if p.ConnectTimeout == nil { // same as the default from consul p.ConnectTimeout = timeToPtr(defaultGatewayConnectTimeout) } if p.EnvoyDNSDiscoveryType == "" { // same as default from consul p.EnvoyDNSDiscoveryType = defaultDNSDiscoveryType } if len(p.EnvoyGatewayBindAddresses) == 0 { p.EnvoyGatewayBindAddresses = nil } if len(p.Config) == 0 { p.Config = nil } } func (p *ConsulGatewayProxy) Copy() *ConsulGatewayProxy { if p == nil { return nil } var binds map[string]*ConsulGatewayBindAddress = nil if p.EnvoyGatewayBindAddresses != nil { binds = make(map[string]*ConsulGatewayBindAddress, len(p.EnvoyGatewayBindAddresses)) for k, v := range p.EnvoyGatewayBindAddresses { binds[k] = v } } var config map[string]interface{} = nil if p.Config != nil { config = make(map[string]interface{}, len(p.Config)) for k, v := range p.Config { config[k] = v } } return &ConsulGatewayProxy{ ConnectTimeout: timeToPtr(*p.ConnectTimeout), EnvoyGatewayBindTaggedAddresses: p.EnvoyGatewayBindTaggedAddresses, EnvoyGatewayBindAddresses: binds, EnvoyGatewayNoDefaultBind: p.EnvoyGatewayNoDefaultBind, EnvoyDNSDiscoveryType: p.EnvoyDNSDiscoveryType, Config: config, } } // ConsulGatewayTLSConfig is used to configure TLS for a gateway. type ConsulGatewayTLSConfig struct { Enabled bool } func (tc *ConsulGatewayTLSConfig) Canonicalize() { } func (tc *ConsulGatewayTLSConfig) Copy() *ConsulGatewayTLSConfig { if tc == nil { return nil } return &ConsulGatewayTLSConfig{ Enabled: tc.Enabled, } } // ConsulIngressService is used to configure a service fronted by the ingress gateway. type ConsulIngressService struct { // Namespace is not yet supported. // Namespace string Name string Hosts []string } func (s *ConsulIngressService) Canonicalize() { if s == nil { return } if len(s.Hosts) == 0 { s.Hosts = nil } } func (s *ConsulIngressService) Copy() *ConsulIngressService { if s == nil { return nil } var hosts []string = nil if n := len(s.Hosts); n > 0 { hosts = make([]string, n) copy(hosts, s.Hosts) } return &ConsulIngressService{ Name: s.Name, Hosts: hosts, } } const ( defaultIngressListenerProtocol = "tcp" ) // ConsulIngressListener is used to configure a listener on a Consul Ingress // Gateway. type ConsulIngressListener struct { Port int Protocol string Services []*ConsulIngressService } func (l *ConsulIngressListener) Canonicalize() { if l == nil { return } if l.Protocol == "" { // same as default from consul l.Protocol = defaultIngressListenerProtocol } if len(l.Services) == 0 { l.Services = nil } } func (l *ConsulIngressListener) Copy() *ConsulIngressListener { if l == nil { return nil } var services []*ConsulIngressService = nil if n := len(l.Services); n > 0 { services = make([]*ConsulIngressService, n) for i := 0; i < n; i++ { services[i] = l.Services[i].Copy() } } return &ConsulIngressListener{ Port: l.Port, Protocol: l.Protocol, Services: services, } } // ConsulIngressConfigEntry represents the Consul Configuration Entry type for // an Ingress Gateway. // // https://www.consul.io/docs/agent/config-entries/ingress-gateway#available-fields type ConsulIngressConfigEntry struct { // Namespace is not yet supported. // Namespace string TLS *ConsulGatewayTLSConfig Listeners []*ConsulIngressListener } func (e *ConsulIngressConfigEntry) Canonicalize() { if e == nil { return } e.TLS.Canonicalize() if len(e.Listeners) == 0 { e.Listeners = nil } for _, listener := range e.Listeners { listener.Canonicalize() } } func (e *ConsulIngressConfigEntry) Copy() *ConsulIngressConfigEntry { if e == nil { return nil } var listeners []*ConsulIngressListener = nil if n := len(e.Listeners); n > 0 { listeners = make([]*ConsulIngressListener, n) for i := 0; i < n; i++ { listeners[i] = e.Listeners[i].Copy() } } return &ConsulIngressConfigEntry{ TLS: e.TLS.Copy(), Listeners: listeners, } } // ConsulTerminatingConfigEntry is not yet supported. // type ConsulTerminatingConfigEntry struct { // } // ConsulMeshConfigEntry is not yet supported. // type ConsulMeshConfigEntry struct { // }