mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
486 lines
15 KiB
486 lines
15 KiB
package structs |
|
|
|
import ( |
|
"encoding/json" |
|
"fmt" |
|
|
|
"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 Upstrea Connect connections |
|
// should be made to a mesh gateway in the local datacenter. This is |
|
MeshGatewayModeLocal MeshGatewayMode = "local" |
|
|
|
// MeshGatewayModeRemote represents that the Upstream Connect connections |
|
// should be made to a mesh gateway in a remote datacenter. |
|
MeshGatewayModeRemote MeshGatewayMode = "remote" |
|
) |
|
|
|
// 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 (base *MeshGatewayConfig) OverlayWith(overlay MeshGatewayConfig) MeshGatewayConfig { |
|
out := *base |
|
if overlay.Mode != MeshGatewayModeDefault { |
|
out.Mode = overlay.Mode |
|
} |
|
return out |
|
} |
|
|
|
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)} |
|
} |
|
|
|
// 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 { |
|
// DestinationServiceName is required and is the name of the service to accept |
|
// traffic for. |
|
DestinationServiceName string `json:",omitempty"` |
|
|
|
// 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"` |
|
|
|
// 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"` |
|
|
|
// 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"` |
|
|
|
// 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"` |
|
} |
|
|
|
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"` |
|
|
|
*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 |
|
} |
|
|
|
return nil |
|
|
|
} |
|
|
|
func (c *ConnectProxyConfig) MarshalJSON() ([]byte, error) { |
|
type typeCopy ConnectProxyConfig |
|
copy := typeCopy(*c) |
|
|
|
proxyConfig, err := lib.MapWalk(copy.Config) |
|
if err != nil { |
|
return nil, err |
|
} |
|
copy.Config = proxyConfig |
|
|
|
return json.Marshal(©) |
|
} |
|
|
|
// 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, |
|
Config: c.Config, |
|
Upstreams: c.Upstreams.ToAPI(), |
|
MeshGateway: c.MeshGateway.ToAPI(), |
|
Expose: c.Expose.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 |
|
DestinationNamespace string `json:",omitempty"` |
|
DestinationName string |
|
|
|
// 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"` |
|
|
|
// LocalBindPort is the ip address a side-car proxy should listen on for traffic |
|
// destined for this upstream service. Required. |
|
LocalBindPort int |
|
|
|
// 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{} `bexpr:"-"` |
|
|
|
// MeshGateway is the configuration for mesh gateway usage of this upstream |
|
MeshGateway MeshGatewayConfig `json:",omitempty"` |
|
|
|
// 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. |
|
IngressHosts []string `json:"-" bexpr:"-"` |
|
} |
|
|
|
func (t *Upstream) UnmarshalJSON(data []byte) (err error) { |
|
type Alias Upstream |
|
aux := &struct { |
|
DestinationTypeSnake string `json:"destination_type"` |
|
DestinationNamespaceSnake string `json:"destination_namespace"` |
|
DestinationNameSnake string `json:"destination_name"` |
|
LocalBindPortSnake int `json:"local_bind_port"` |
|
LocalBindAddressSnake string `json:"local_bind_address"` |
|
|
|
*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.DestinationName == "" { |
|
t.DestinationName = aux.DestinationNameSnake |
|
} |
|
if t.LocalBindPort == 0 { |
|
t.LocalBindPort = aux.LocalBindPortSnake |
|
} |
|
if t.LocalBindAddress == "" { |
|
t.LocalBindAddress = aux.LocalBindAddressSnake |
|
} |
|
|
|
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.LocalBindPort == 0 { |
|
return fmt.Errorf("upstream local bind port cannot be zero") |
|
} |
|
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, |
|
DestinationName: u.DestinationName, |
|
Datacenter: u.Datacenter, |
|
LocalBindAddress: u.LocalBindAddress, |
|
LocalBindPort: u.LocalBindPort, |
|
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-specificed explicit values and not inferred |
|
// values. |
|
func (u *Upstream) ToKey() UpstreamKey { |
|
return UpstreamKey{ |
|
DestinationType: u.DestinationType, |
|
DestinationNamespace: u.DestinationNamespace, |
|
DestinationName: u.DestinationName, |
|
Datacenter: u.Datacenter, |
|
} |
|
} |
|
|
|
type UpstreamKey struct { |
|
DestinationType string |
|
DestinationName string |
|
DestinationNamespace string |
|
Datacenter string |
|
} |
|
|
|
func (k UpstreamKey) String() string { |
|
return fmt.Sprintf( |
|
"[type=%q, name=%q, namespace=%q, datacenter=%q]", |
|
k.DestinationType, |
|
k.DestinationName, |
|
k.DestinationNamespace, |
|
k.Datacenter, |
|
) |
|
} |
|
|
|
// Identifier returns a string representation that uniquely identifies the |
|
// upstream in a canonical but human readable way. |
|
func (u *Upstream) Identifier() string { |
|
name := u.DestinationName |
|
typ := u.DestinationType |
|
|
|
if typ != UpstreamDestTypePreparedQuery && u.DestinationNamespace != "" && u.DestinationNamespace != IntentionDefaultNamespace { |
|
name = u.DestinationNamespace + "/" + u.DestinationName |
|
} |
|
if u.Datacenter != "" { |
|
name += "?dc=" + u.Datacenter |
|
} |
|
|
|
// Service is default type so never prefix it. This is more readable and long |
|
// term it is the only type that matters so we can drop the prefix and have |
|
// nicer naming in metrics etc. |
|
if typ == "" || typ == UpstreamDestTypeService { |
|
return name |
|
} |
|
return typ + ":" + name |
|
} |
|
|
|
// String implements Stringer by returning the Identifier. |
|
func (u *Upstream) String() string { |
|
return u.Identifier() |
|
} |
|
|
|
// UpstreamFromAPI is a helper for converting api.Upstream to Upstream. |
|
func UpstreamFromAPI(u api.Upstream) Upstream { |
|
return Upstream{ |
|
DestinationType: string(u.DestinationType), |
|
DestinationNamespace: u.DestinationNamespace, |
|
DestinationName: u.DestinationName, |
|
Datacenter: u.Datacenter, |
|
LocalBindAddress: u.LocalBindAddress, |
|
LocalBindPort: u.LocalBindPort, |
|
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"` |
|
} |
|
|
|
type ExposePath struct { |
|
// ListenerPort defines the port of the proxy's listener for exposed paths. |
|
ListenerPort int `json:",omitempty" alias:"listener_port"` |
|
|
|
// ExposePath 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 |
|
} |
|
|
|
func (t *ExposePath) UnmarshalJSON(data []byte) (err error) { |
|
type Alias ExposePath |
|
aux := &struct { |
|
LocalPathPortSnake int `json:"local_path_port"` |
|
ListenerPortSnake int `json:"listener_port"` |
|
|
|
*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 |
|
} |
|
|
|
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 |
|
} |
|
} |
|
}
|
|
|