38d65efb72
* feat: access logs for listeners and listener filters * changelog * fix integration test
802 lines
27 KiB
Go
802 lines
27 KiB
Go
package structs
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/hashicorp/consul/api"
|
|
"github.com/hashicorp/consul/lib"
|
|
)
|
|
|
|
const (
|
|
defaultExposeProtocol = "http"
|
|
)
|
|
|
|
var allowedExposeProtocols = map[string]bool{"http": true, "http2": true}
|
|
|
|
type MeshGatewayMode string
|
|
|
|
const (
|
|
// MeshGatewayModeDefault represents no specific mode and should
|
|
// be used to indicate that a different layer of the configuration
|
|
// chain should take precedence
|
|
MeshGatewayModeDefault MeshGatewayMode = ""
|
|
|
|
// MeshGatewayModeNone represents that the Upstream Connect connections
|
|
// should be direct and not flow through a mesh gateway.
|
|
MeshGatewayModeNone MeshGatewayMode = "none"
|
|
|
|
// MeshGatewayModeLocal represents that the Upstream Connect connections
|
|
// should be made to a mesh gateway in the local datacenter.
|
|
MeshGatewayModeLocal MeshGatewayMode = "local"
|
|
|
|
// MeshGatewayModeRemote represents that the Upstream Connect connections
|
|
// should be made to a mesh gateway in a remote datacenter.
|
|
MeshGatewayModeRemote MeshGatewayMode = "remote"
|
|
)
|
|
|
|
type LogSinkType string
|
|
|
|
const (
|
|
DefaultLogSinkType LogSinkType = ""
|
|
FileLogSinkType LogSinkType = "file"
|
|
StdErrLogSinkType LogSinkType = "stderr"
|
|
StdOutLogSinkType LogSinkType = "stdout"
|
|
)
|
|
|
|
const (
|
|
// TODO (freddy) Should we have a TopologySourceMixed when there is a mix of proxy reg and tproxy?
|
|
// Currently we label as proxy-registration if ANY instance has the explicit upstream definition.
|
|
// TopologySourceRegistration is used to label upstreams or downstreams from explicit upstream definitions.
|
|
TopologySourceRegistration = "proxy-registration"
|
|
|
|
// TopologySourceSpecificIntention is used to label upstreams or downstreams from specific intentions.
|
|
TopologySourceSpecificIntention = "specific-intention"
|
|
|
|
// TopologySourceWildcardIntention is used to label upstreams or downstreams from wildcard intentions.
|
|
TopologySourceWildcardIntention = "wildcard-intention"
|
|
|
|
// TopologySourceDefaultAllow is used to label upstreams or downstreams from default allow ACL policy.
|
|
TopologySourceDefaultAllow = "default-allow"
|
|
|
|
// TopologySourceRoutingConfig is used to label upstreams that are not backed by a service instance
|
|
// and are simply used for routing configurations.
|
|
TopologySourceRoutingConfig = "routing-config"
|
|
)
|
|
|
|
// MeshGatewayConfig controls how Mesh Gateways are configured and used
|
|
// This is a struct to allow for future additions without having more free-hanging
|
|
// configuration items all over the place
|
|
type MeshGatewayConfig struct {
|
|
// The Mesh Gateway routing mode
|
|
Mode MeshGatewayMode `json:",omitempty"`
|
|
}
|
|
|
|
func (c *MeshGatewayConfig) IsZero() bool {
|
|
zeroVal := MeshGatewayConfig{}
|
|
return *c == zeroVal
|
|
}
|
|
|
|
func ValidateMeshGatewayMode(mode string) (MeshGatewayMode, error) {
|
|
switch MeshGatewayMode(mode) {
|
|
case MeshGatewayModeNone:
|
|
return MeshGatewayModeNone, nil
|
|
case MeshGatewayModeDefault:
|
|
return MeshGatewayModeDefault, nil
|
|
case MeshGatewayModeLocal:
|
|
return MeshGatewayModeLocal, nil
|
|
case MeshGatewayModeRemote:
|
|
return MeshGatewayModeRemote, nil
|
|
default:
|
|
return MeshGatewayModeDefault, fmt.Errorf("Invalid Mesh Gateway Mode: %q", mode)
|
|
}
|
|
}
|
|
|
|
func (c *MeshGatewayConfig) ToAPI() api.MeshGatewayConfig {
|
|
return api.MeshGatewayConfig{Mode: api.MeshGatewayMode(c.Mode)}
|
|
}
|
|
|
|
type ProxyMode string
|
|
|
|
const (
|
|
// ProxyModeDefault represents no specific mode and should
|
|
// be used to indicate that a different layer of the configuration
|
|
// chain should take precedence
|
|
ProxyModeDefault ProxyMode = ""
|
|
|
|
// ProxyModeTransparent represents that inbound and outbound application
|
|
// traffic is being captured and redirected through the proxy.
|
|
ProxyModeTransparent ProxyMode = "transparent"
|
|
|
|
// ProxyModeDirect represents that the proxy's listeners must be dialed directly
|
|
// by the local application and other proxies.
|
|
ProxyModeDirect ProxyMode = "direct"
|
|
)
|
|
|
|
func ValidateProxyMode(mode string) (ProxyMode, error) {
|
|
switch ProxyMode(mode) {
|
|
case ProxyModeDefault:
|
|
return ProxyModeDefault, nil
|
|
case ProxyModeDirect:
|
|
return ProxyModeDirect, nil
|
|
case ProxyModeTransparent:
|
|
return ProxyModeTransparent, nil
|
|
default:
|
|
return ProxyModeDefault, fmt.Errorf("Invalid Proxy Mode: %q", mode)
|
|
}
|
|
}
|
|
|
|
type TransparentProxyConfig struct {
|
|
// The port of the listener where outbound application traffic is being redirected to.
|
|
OutboundListenerPort int `json:",omitempty" alias:"outbound_listener_port"`
|
|
|
|
// DialedDirectly indicates whether transparent proxies can dial this proxy instance directly.
|
|
// The discovery chain is not considered when dialing a service instance directly.
|
|
// This setting is useful when addressing stateful services, such as a database cluster with a leader node.
|
|
DialedDirectly bool `json:",omitempty" alias:"dialed_directly"`
|
|
}
|
|
|
|
func (c TransparentProxyConfig) ToAPI() *api.TransparentProxyConfig {
|
|
if c.IsZero() {
|
|
return nil
|
|
}
|
|
return &api.TransparentProxyConfig{
|
|
OutboundListenerPort: c.OutboundListenerPort,
|
|
DialedDirectly: c.DialedDirectly,
|
|
}
|
|
}
|
|
|
|
func (c *TransparentProxyConfig) IsZero() bool {
|
|
if c == nil {
|
|
return true
|
|
}
|
|
zeroVal := TransparentProxyConfig{}
|
|
return *c == zeroVal
|
|
}
|
|
|
|
// AccessLogsConfig contains the associated default settings for all Envoy instances within the datacenter or partition
|
|
type AccessLogsConfig struct {
|
|
// Enabled turns off all access logging
|
|
Enabled bool `json:",omitempty" alias:"enabled"`
|
|
|
|
// DisableListenerLogs turns off just listener logs for connections rejected by Envoy because they don't
|
|
// have a matching listener filter.
|
|
DisableListenerLogs bool `json:",omitempty" alias:"disable_listener_logs"`
|
|
|
|
// Type selects the output for logs: "file", "stderr". "stdout"
|
|
Type LogSinkType `json:",omitempty" alias:"type"`
|
|
|
|
// Path is the output file to write logs
|
|
Path string `json:",omitempty" alias:"path"`
|
|
|
|
// The presence of one format string or the other implies the access log string encoding.
|
|
// Defining Both is invalid.
|
|
JSONFormat string `json:",omitempty" alias:"json_format"`
|
|
TextFormat string `json:",omitempty" alias:"text_format"`
|
|
}
|
|
|
|
func (c *AccessLogsConfig) IsZero() bool {
|
|
if c == nil {
|
|
return true
|
|
}
|
|
zeroVal := AccessLogsConfig{}
|
|
return *c == zeroVal
|
|
}
|
|
|
|
func (c *AccessLogsConfig) ToAPI() *api.AccessLogsConfig {
|
|
if c.IsZero() {
|
|
return nil
|
|
}
|
|
return &api.AccessLogsConfig{
|
|
Enabled: c.Enabled,
|
|
DisableListenerLogs: c.DisableListenerLogs,
|
|
Type: api.LogSinkType(c.Type),
|
|
Path: c.Path,
|
|
JSONFormat: c.JSONFormat,
|
|
TextFormat: c.TextFormat,
|
|
}
|
|
}
|
|
|
|
func (c *AccessLogsConfig) Validate() error {
|
|
switch c.Type {
|
|
case DefaultLogSinkType, StdErrLogSinkType, StdOutLogSinkType:
|
|
// OK
|
|
case FileLogSinkType:
|
|
if c.Path == "" {
|
|
return errors.New("path must be specified when using file type access logs")
|
|
}
|
|
default:
|
|
return fmt.Errorf("invalid access log type: %s", c.Type)
|
|
}
|
|
|
|
if c.JSONFormat != "" && c.TextFormat != "" {
|
|
return errors.New("cannot specify both access log JSONFormat and TextFormat")
|
|
}
|
|
|
|
if c.Type != FileLogSinkType && c.Path != "" {
|
|
return errors.New("path is only valid for file type access logs")
|
|
}
|
|
|
|
if c.JSONFormat != "" {
|
|
msg := json.RawMessage{}
|
|
if err := json.Unmarshal([]byte(c.JSONFormat), &msg); err != nil {
|
|
return fmt.Errorf("invalid access log json for JSON format: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ConnectProxyConfig describes the configuration needed for any proxy managed
|
|
// or unmanaged. It describes a single logical service's listener and optionally
|
|
// upstreams and sidecar-related config for a single instance. To describe a
|
|
// centralized proxy that routed traffic for multiple services, a different one
|
|
// of these would be needed for each, sharing the same LogicalProxyID.
|
|
type ConnectProxyConfig struct {
|
|
// EnvoyExtensions are the list of Envoy extensions configured for the local service.
|
|
EnvoyExtensions []EnvoyExtension `json:",omitempty" alias:"envoy_extensions"`
|
|
|
|
// DestinationServiceName is required and is the name of the service to accept
|
|
// traffic for.
|
|
DestinationServiceName string `json:",omitempty" alias:"destination_service_name"`
|
|
|
|
// DestinationServiceID is optional and should only be specified for
|
|
// "side-car" style proxies where the proxy is in front of just a single
|
|
// instance of the service. It should be set to the service ID of the instance
|
|
// being represented which must be registered to the same agent. It's valid to
|
|
// provide a service ID that does not yet exist to avoid timing issues when
|
|
// bootstrapping a service with a proxy.
|
|
DestinationServiceID string `json:",omitempty" alias:"destination_service_id"`
|
|
|
|
// LocalServiceAddress is the address of the local service instance. It is
|
|
// optional and should only be specified for "side-car" style proxies. It will
|
|
// default to 127.0.0.1 if the proxy is a "side-car" (DestinationServiceID is
|
|
// set) but otherwise will be ignored.
|
|
LocalServiceAddress string `json:",omitempty" alias:"local_service_address"`
|
|
|
|
// LocalServicePort is the port of the local service instance. It is optional
|
|
// and should only be specified for "side-car" style proxies. It will default
|
|
// to the registered port for the instance if the proxy is a "side-car"
|
|
// (DestinationServiceID is set) but otherwise will be ignored.
|
|
LocalServicePort int `json:",omitempty" alias:"local_service_port"`
|
|
|
|
// LocalServiceSocketPath is the socket of the local service instance. It is optional
|
|
// and should only be specified for "side-car" style proxies.
|
|
LocalServiceSocketPath string `json:",omitempty" alias:"local_service_socket_path"`
|
|
|
|
// Mode represents how the proxy's inbound and upstream listeners are dialed.
|
|
Mode ProxyMode
|
|
|
|
// Config is the arbitrary configuration data provided with the proxy
|
|
// registration.
|
|
Config map[string]interface{} `json:",omitempty" bexpr:"-"`
|
|
|
|
// Upstreams describes any upstream dependencies the proxy instance should
|
|
// setup.
|
|
Upstreams Upstreams `json:",omitempty"`
|
|
|
|
// MeshGateway defines the mesh gateway configuration for this upstream
|
|
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
|
|
|
|
// Expose defines whether checks or paths are exposed through the proxy
|
|
Expose ExposeConfig `json:",omitempty"`
|
|
|
|
// TransparentProxy defines configuration for when the proxy is in
|
|
// transparent mode.
|
|
TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
|
|
|
|
// AccessLogs configures the output and format of Envoy access logs
|
|
AccessLogs AccessLogsConfig `json:",omitempty" alias:"access_logs"`
|
|
}
|
|
|
|
func (t *ConnectProxyConfig) UnmarshalJSON(data []byte) (err error) {
|
|
type Alias ConnectProxyConfig
|
|
aux := &struct {
|
|
DestinationServiceNameSnake string `json:"destination_service_name"`
|
|
DestinationServiceIDSnake string `json:"destination_service_id"`
|
|
LocalServiceAddressSnake string `json:"local_service_address"`
|
|
LocalServicePortSnake int `json:"local_service_port"`
|
|
LocalServiceSocketPathSnake string `json:"local_service_socket_path"`
|
|
MeshGatewaySnake MeshGatewayConfig `json:"mesh_gateway"`
|
|
TransparentProxySnake TransparentProxyConfig `json:"transparent_proxy"`
|
|
AccessLogsSnake AccessLogsConfig `json:"access_logs"`
|
|
*Alias
|
|
}{
|
|
Alias: (*Alias)(t),
|
|
}
|
|
if err = lib.UnmarshalJSON(data, &aux); err != nil {
|
|
return err
|
|
}
|
|
if t.DestinationServiceName == "" {
|
|
t.DestinationServiceName = aux.DestinationServiceNameSnake
|
|
}
|
|
if t.DestinationServiceID == "" {
|
|
t.DestinationServiceID = aux.DestinationServiceIDSnake
|
|
}
|
|
if t.LocalServiceAddress == "" {
|
|
t.LocalServiceAddress = aux.LocalServiceAddressSnake
|
|
}
|
|
if t.LocalServicePort == 0 {
|
|
t.LocalServicePort = aux.LocalServicePortSnake
|
|
}
|
|
if t.LocalServiceSocketPath == "" {
|
|
t.LocalServiceSocketPath = aux.LocalServiceSocketPathSnake
|
|
}
|
|
if t.MeshGateway.Mode == "" {
|
|
t.MeshGateway.Mode = aux.MeshGatewaySnake.Mode
|
|
}
|
|
if t.TransparentProxy.OutboundListenerPort == 0 {
|
|
t.TransparentProxy.OutboundListenerPort = aux.TransparentProxySnake.OutboundListenerPort
|
|
}
|
|
if !t.TransparentProxy.DialedDirectly {
|
|
t.TransparentProxy.DialedDirectly = aux.TransparentProxySnake.DialedDirectly
|
|
}
|
|
if !t.AccessLogs.Enabled {
|
|
t.AccessLogs.Enabled = aux.AccessLogsSnake.Enabled
|
|
}
|
|
if !t.AccessLogs.DisableListenerLogs {
|
|
t.AccessLogs.DisableListenerLogs = aux.AccessLogsSnake.DisableListenerLogs
|
|
}
|
|
if t.AccessLogs.Type == "" {
|
|
t.AccessLogs.Type = aux.AccessLogsSnake.Type
|
|
}
|
|
if t.AccessLogs.Path == "" {
|
|
t.AccessLogs.Path = aux.AccessLogsSnake.Path
|
|
}
|
|
if t.AccessLogs.JSONFormat == "" {
|
|
t.AccessLogs.JSONFormat = aux.AccessLogsSnake.JSONFormat
|
|
}
|
|
if t.AccessLogs.TextFormat == "" {
|
|
t.AccessLogs.TextFormat = aux.AccessLogsSnake.TextFormat
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *ConnectProxyConfig) MarshalJSON() ([]byte, error) {
|
|
type Alias ConnectProxyConfig
|
|
out := struct {
|
|
TransparentProxy *TransparentProxyConfig `json:",omitempty"`
|
|
AccessLogs *AccessLogsConfig `json:",omitempty"`
|
|
Alias
|
|
}{
|
|
Alias: (Alias)(*c),
|
|
}
|
|
|
|
proxyConfig, err := lib.MapWalk(c.Config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out.Alias.Config = proxyConfig
|
|
|
|
if !c.TransparentProxy.IsZero() {
|
|
out.TransparentProxy = &out.Alias.TransparentProxy
|
|
}
|
|
|
|
if !c.AccessLogs.IsZero() {
|
|
out.AccessLogs = &out.Alias.AccessLogs
|
|
}
|
|
|
|
return json.Marshal(&out)
|
|
}
|
|
|
|
// ToAPI returns the api struct with the same fields. We have duplicates to
|
|
// avoid the api package depending on this one which imports a ton of Consul's
|
|
// core which you don't want if you are just trying to use our client in your
|
|
// app.
|
|
func (c *ConnectProxyConfig) ToAPI() *api.AgentServiceConnectProxyConfig {
|
|
return &api.AgentServiceConnectProxyConfig{
|
|
DestinationServiceName: c.DestinationServiceName,
|
|
DestinationServiceID: c.DestinationServiceID,
|
|
LocalServiceAddress: c.LocalServiceAddress,
|
|
LocalServicePort: c.LocalServicePort,
|
|
LocalServiceSocketPath: c.LocalServiceSocketPath,
|
|
Mode: api.ProxyMode(c.Mode),
|
|
TransparentProxy: c.TransparentProxy.ToAPI(),
|
|
Config: c.Config,
|
|
Upstreams: c.Upstreams.ToAPI(),
|
|
MeshGateway: c.MeshGateway.ToAPI(),
|
|
Expose: c.Expose.ToAPI(),
|
|
AccessLogs: c.AccessLogs.ToAPI(),
|
|
}
|
|
}
|
|
|
|
const (
|
|
UpstreamDestTypeService = "service"
|
|
UpstreamDestTypePreparedQuery = "prepared_query"
|
|
)
|
|
|
|
// Upstreams is a list of upstreams. Aliased to allow ToAPI method.
|
|
type Upstreams []Upstream
|
|
|
|
// ToAPI returns the api structs with the same fields. We have duplicates to
|
|
// avoid the api package depending on this one which imports a ton of Consul's
|
|
// core which you don't want if you are just trying to use our client in your
|
|
// app.
|
|
func (us Upstreams) ToAPI() []api.Upstream {
|
|
a := make([]api.Upstream, len(us))
|
|
for i, u := range us {
|
|
a[i] = u.ToAPI()
|
|
}
|
|
return a
|
|
}
|
|
|
|
// UpstreamsFromAPI is a helper for converting api.Upstream to Upstream.
|
|
func UpstreamsFromAPI(us []api.Upstream) Upstreams {
|
|
a := make([]Upstream, len(us))
|
|
for i, u := range us {
|
|
a[i] = UpstreamFromAPI(u)
|
|
}
|
|
return a
|
|
}
|
|
|
|
// Upstream represents a single upstream dependency for a service or proxy. It
|
|
// describes the mechanism used to discover instances to communicate with (the
|
|
// Target) as well as any potential client configuration that may be useful such
|
|
// as load balancer options, timeouts etc.
|
|
type Upstream struct {
|
|
// Destination fields are the required ones for determining what this upstream
|
|
// points to. Depending on DestinationType some other fields below might
|
|
// further restrict the set of instances allowable.
|
|
//
|
|
// DestinationType would be better as an int constant but even with custom
|
|
// JSON marshallers it causes havoc with all the mapstructure mangling we do
|
|
// on service definitions in various places.
|
|
DestinationType string `alias:"destination_type"`
|
|
DestinationNamespace string `json:",omitempty" alias:"destination_namespace"`
|
|
DestinationPartition string `json:",omitempty" alias:"destination_partition"`
|
|
DestinationPeer string `json:",omitempty" alias:"destination_peer"`
|
|
DestinationName string `alias:"destination_name"`
|
|
|
|
// Datacenter that the service discovery request should be run against. Note
|
|
// for prepared queries, the actual results might be from a different
|
|
// datacenter.
|
|
Datacenter string
|
|
|
|
// LocalBindAddress is the ip address a side-car proxy should listen on for
|
|
// traffic destined for this upstream service. Default if empty is 127.0.0.1.
|
|
LocalBindAddress string `json:",omitempty" alias:"local_bind_address"`
|
|
|
|
// LocalBindPort is the ip address a side-car proxy should listen on for traffic
|
|
// destined for this upstream service. Required.
|
|
LocalBindPort int `json:",omitempty" alias:"local_bind_port"`
|
|
|
|
// These are exclusive with LocalBindAddress/LocalBindPort
|
|
LocalBindSocketPath string `json:",omitempty" alias:"local_bind_socket_path"`
|
|
// This might be represented as an int, but because it's octal outputs can be a bit strange.
|
|
LocalBindSocketMode string `json:",omitempty" alias:"local_bind_socket_mode"`
|
|
|
|
// Config is an opaque config that is specific to the proxy process being run.
|
|
// It can be used to pass arbitrary configuration for this specific upstream
|
|
// to the proxy.
|
|
Config map[string]interface{} `json:",omitempty" bexpr:"-"`
|
|
|
|
// MeshGateway is the configuration for mesh gateway usage of this upstream
|
|
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
|
|
|
|
// IngressHosts are a list of hosts that should route to this upstream from an
|
|
// ingress gateway. This cannot and should not be set by a user, it is used
|
|
// internally to store the association of hosts to an upstream service.
|
|
// TODO(banks): we shouldn't need this any more now we pass through full
|
|
// listener config in the ingress snapshot.
|
|
IngressHosts []string `json:"-" bexpr:"-"`
|
|
|
|
// CentrallyConfigured indicates whether the upstream was defined in a proxy
|
|
// instance registration or whether it was generated from a config entry.
|
|
CentrallyConfigured bool `json:",omitempty" bexpr:"-"`
|
|
}
|
|
|
|
func (t *Upstream) UnmarshalJSON(data []byte) (err error) {
|
|
type Alias Upstream
|
|
aux := &struct {
|
|
DestinationTypeSnake string `json:"destination_type"`
|
|
DestinationPartitionSnake string `json:"destination_partition"`
|
|
DestinationNamespaceSnake string `json:"destination_namespace"`
|
|
DestinationPeerSnake string `json:"destination_peer"`
|
|
DestinationNameSnake string `json:"destination_name"`
|
|
|
|
LocalBindAddressSnake string `json:"local_bind_address"`
|
|
LocalBindPortSnake int `json:"local_bind_port"`
|
|
|
|
LocalBindSocketPathSnake string `json:"local_bind_socket_path"`
|
|
LocalBindSocketModeSnake string `json:"local_bind_socket_mode"`
|
|
|
|
MeshGatewaySnake MeshGatewayConfig `json:"mesh_gateway"`
|
|
|
|
*Alias
|
|
}{
|
|
Alias: (*Alias)(t),
|
|
}
|
|
if err = lib.UnmarshalJSON(data, &aux); err != nil {
|
|
return err
|
|
}
|
|
if t.DestinationType == "" {
|
|
t.DestinationType = aux.DestinationTypeSnake
|
|
}
|
|
if t.DestinationNamespace == "" {
|
|
t.DestinationNamespace = aux.DestinationNamespaceSnake
|
|
}
|
|
if t.DestinationPartition == "" {
|
|
t.DestinationPartition = aux.DestinationPartitionSnake
|
|
}
|
|
if t.DestinationPeer == "" {
|
|
t.DestinationPeer = aux.DestinationPeerSnake
|
|
}
|
|
if t.DestinationName == "" {
|
|
t.DestinationName = aux.DestinationNameSnake
|
|
}
|
|
if t.LocalBindAddress == "" {
|
|
t.LocalBindAddress = aux.LocalBindAddressSnake
|
|
}
|
|
if t.LocalBindPort == 0 {
|
|
t.LocalBindPort = aux.LocalBindPortSnake
|
|
}
|
|
if t.LocalBindSocketPath == "" {
|
|
t.LocalBindSocketPath = aux.LocalBindSocketPathSnake
|
|
}
|
|
if t.LocalBindSocketMode == "" {
|
|
t.LocalBindSocketMode = aux.LocalBindSocketModeSnake
|
|
}
|
|
if t.MeshGateway.Mode == "" {
|
|
t.MeshGateway.Mode = aux.MeshGatewaySnake.Mode
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate sanity checks the struct is valid
|
|
func (u *Upstream) Validate() error {
|
|
switch u.DestinationType {
|
|
case UpstreamDestTypePreparedQuery:
|
|
case UpstreamDestTypeService, "":
|
|
default:
|
|
return fmt.Errorf("unknown upstream destination type: %q", u.DestinationType)
|
|
}
|
|
|
|
if u.DestinationName == "" {
|
|
return fmt.Errorf("upstream destination name cannot be empty")
|
|
}
|
|
if u.DestinationName == WildcardSpecifier && !u.CentrallyConfigured {
|
|
return fmt.Errorf("upstream destination name cannot be a wildcard")
|
|
}
|
|
if u.DestinationPeer != "" && u.Datacenter != "" {
|
|
return fmt.Errorf("upstream cannot specify both destination peer and datacenter")
|
|
}
|
|
|
|
if u.LocalBindPort == 0 && u.LocalBindSocketPath == "" && !u.CentrallyConfigured {
|
|
return fmt.Errorf("upstream local bind port or local socket path must be defined and nonzero")
|
|
}
|
|
if u.LocalBindPort != 0 && u.LocalBindSocketPath != "" && !u.CentrallyConfigured {
|
|
return fmt.Errorf("only one of upstream local bind port or local socket path can be defined and nonzero")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ToAPI returns the api structs with the same fields. We have duplicates to
|
|
// avoid the api package depending on this one which imports a ton of Consul's
|
|
// core which you don't want if you are just trying to use our client in your
|
|
// app.
|
|
func (u *Upstream) ToAPI() api.Upstream {
|
|
return api.Upstream{
|
|
DestinationType: api.UpstreamDestType(u.DestinationType),
|
|
DestinationNamespace: u.DestinationNamespace,
|
|
DestinationPartition: u.DestinationPartition,
|
|
DestinationPeer: u.DestinationPeer,
|
|
DestinationName: u.DestinationName,
|
|
Datacenter: u.Datacenter,
|
|
LocalBindAddress: u.LocalBindAddress,
|
|
LocalBindPort: u.LocalBindPort,
|
|
LocalBindSocketPath: u.LocalBindSocketPath,
|
|
LocalBindSocketMode: u.LocalBindSocketMode,
|
|
Config: u.Config,
|
|
MeshGateway: u.MeshGateway.ToAPI(),
|
|
}
|
|
}
|
|
|
|
// ToKey returns a value-type representation that uniquely identifies the
|
|
// upstream in a canonical way. Set and unset values are deliberately handled
|
|
// differently.
|
|
//
|
|
// These fields should be user-specified explicit values and not inferred
|
|
// values.
|
|
func (u *Upstream) ToKey() UpstreamKey {
|
|
return UpstreamKey{
|
|
DestinationType: u.DestinationType,
|
|
DestinationPartition: u.DestinationPartition,
|
|
DestinationNamespace: u.DestinationNamespace,
|
|
DestinationPeer: u.DestinationPeer,
|
|
DestinationName: u.DestinationName,
|
|
Datacenter: u.Datacenter,
|
|
}
|
|
}
|
|
|
|
func (u *Upstream) HasLocalPortOrSocket() bool {
|
|
if u == nil {
|
|
return false
|
|
}
|
|
return (u.LocalBindPort != 0 || u.LocalBindSocketPath != "")
|
|
}
|
|
|
|
func (u *Upstream) UpstreamIsUnixSocket() bool {
|
|
if u == nil {
|
|
return false
|
|
}
|
|
return (u.LocalBindPort == 0 && u.LocalBindAddress == "" && u.LocalBindSocketPath != "")
|
|
}
|
|
|
|
func (u *Upstream) UpstreamAddressToString() string {
|
|
if u == nil {
|
|
return ""
|
|
}
|
|
if u.UpstreamIsUnixSocket() {
|
|
return u.LocalBindSocketPath
|
|
}
|
|
|
|
addr := u.LocalBindAddress
|
|
if addr == "" {
|
|
addr = "127.0.0.1"
|
|
}
|
|
return net.JoinHostPort(addr, fmt.Sprintf("%d", u.LocalBindPort))
|
|
}
|
|
|
|
type UpstreamKey struct {
|
|
DestinationType string
|
|
DestinationName string
|
|
DestinationPartition string
|
|
DestinationNamespace string
|
|
DestinationPeer string
|
|
Datacenter string
|
|
}
|
|
|
|
func (k UpstreamKey) String() string {
|
|
return fmt.Sprintf(
|
|
"[type=%q, name=%q, partition=%q, namespace=%q, peer=%q, datacenter=%q]",
|
|
k.DestinationType,
|
|
k.DestinationName,
|
|
k.DestinationPartition,
|
|
k.DestinationNamespace,
|
|
k.DestinationPeer,
|
|
k.Datacenter,
|
|
)
|
|
}
|
|
|
|
// String returns a representation of this upstream suitable for debugging
|
|
// purposes but nothing relies upon this format.
|
|
func (us *Upstream) String() string {
|
|
name := us.enterpriseStringPrefix() + us.DestinationName
|
|
typ := us.DestinationType
|
|
|
|
if us.DestinationPeer != "" {
|
|
name += "?peer=" + us.DestinationPeer
|
|
} else if us.Datacenter != "" {
|
|
name += "?dc=" + us.Datacenter
|
|
}
|
|
|
|
// Service is default type so never prefix it.
|
|
if typ == "" || typ == UpstreamDestTypeService {
|
|
return name
|
|
}
|
|
return typ + ":" + name
|
|
}
|
|
|
|
// UpstreamFromAPI is a helper for converting api.Upstream to Upstream.
|
|
func UpstreamFromAPI(u api.Upstream) Upstream {
|
|
return Upstream{
|
|
DestinationType: string(u.DestinationType),
|
|
DestinationPartition: u.DestinationPartition,
|
|
DestinationNamespace: u.DestinationNamespace,
|
|
DestinationPeer: u.DestinationPeer,
|
|
DestinationName: u.DestinationName,
|
|
Datacenter: u.Datacenter,
|
|
LocalBindAddress: u.LocalBindAddress,
|
|
LocalBindPort: u.LocalBindPort,
|
|
LocalBindSocketPath: u.LocalBindSocketPath,
|
|
LocalBindSocketMode: u.LocalBindSocketMode,
|
|
Config: u.Config,
|
|
}
|
|
}
|
|
|
|
// ExposeConfig describes HTTP paths to expose through Envoy outside of Connect.
|
|
// Users can expose individual paths and/or all HTTP/GRPC paths for checks.
|
|
type ExposeConfig struct {
|
|
// Checks defines whether paths associated with Consul checks will be exposed.
|
|
// This flag triggers exposing all HTTP and GRPC check paths registered for the service.
|
|
Checks bool `json:",omitempty"`
|
|
|
|
// Paths is the list of paths exposed through the proxy.
|
|
Paths []ExposePath `json:",omitempty"`
|
|
}
|
|
|
|
func (e ExposeConfig) Clone() ExposeConfig {
|
|
e2 := e
|
|
if len(e.Paths) > 0 {
|
|
e2.Paths = make([]ExposePath, 0, len(e.Paths))
|
|
for _, p := range e.Paths {
|
|
e2.Paths = append(e2.Paths, p)
|
|
}
|
|
}
|
|
return e2
|
|
}
|
|
|
|
type ExposePath struct {
|
|
// ListenerPort defines the port of the proxy's listener for exposed paths.
|
|
ListenerPort int `json:",omitempty" alias:"listener_port"`
|
|
|
|
// Path is the path to expose through the proxy, ie. "/metrics."
|
|
Path string `json:",omitempty"`
|
|
|
|
// LocalPathPort is the port that the service is listening on for the given path.
|
|
LocalPathPort int `json:",omitempty" alias:"local_path_port"`
|
|
|
|
// Protocol describes the upstream's service protocol.
|
|
// Valid values are "http" and "http2", defaults to "http"
|
|
Protocol string `json:",omitempty"`
|
|
|
|
// ParsedFromCheck is set if this path was parsed from a registered check
|
|
ParsedFromCheck bool `json:",omitempty" alias:"parsed_from_check"`
|
|
}
|
|
|
|
func (t *ExposePath) UnmarshalJSON(data []byte) (err error) {
|
|
type Alias ExposePath
|
|
aux := &struct {
|
|
ListenerPortSnake int `json:"listener_port"`
|
|
LocalPathPortSnake int `json:"local_path_port"`
|
|
ParsedFromCheckSnake bool `json:"parsed_from_check"`
|
|
|
|
*Alias
|
|
}{
|
|
Alias: (*Alias)(t),
|
|
}
|
|
if err = lib.UnmarshalJSON(data, &aux); err != nil {
|
|
return err
|
|
}
|
|
if t.LocalPathPort == 0 {
|
|
t.LocalPathPort = aux.LocalPathPortSnake
|
|
}
|
|
if t.ListenerPort == 0 {
|
|
t.ListenerPort = aux.ListenerPortSnake
|
|
}
|
|
if aux.ParsedFromCheckSnake {
|
|
t.ParsedFromCheck = true
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *ExposeConfig) ToAPI() api.ExposeConfig {
|
|
paths := make([]api.ExposePath, 0)
|
|
for _, p := range e.Paths {
|
|
paths = append(paths, p.ToAPI())
|
|
}
|
|
if e.Paths == nil {
|
|
paths = nil
|
|
}
|
|
|
|
return api.ExposeConfig{
|
|
Checks: e.Checks,
|
|
Paths: paths,
|
|
}
|
|
}
|
|
|
|
func (p *ExposePath) ToAPI() api.ExposePath {
|
|
return api.ExposePath{
|
|
ListenerPort: p.ListenerPort,
|
|
Path: p.Path,
|
|
LocalPathPort: p.LocalPathPort,
|
|
Protocol: p.Protocol,
|
|
ParsedFromCheck: p.ParsedFromCheck,
|
|
}
|
|
}
|
|
|
|
// Finalize validates ExposeConfig and sets default values
|
|
func (e *ExposeConfig) Finalize() {
|
|
for i := 0; i < len(e.Paths); i++ {
|
|
path := &e.Paths[i]
|
|
|
|
if path.Protocol == "" {
|
|
path.Protocol = defaultExposeProtocol
|
|
}
|
|
}
|
|
}
|