140 lines
4.8 KiB
Go
140 lines
4.8 KiB
Go
package gateways
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
)
|
|
|
|
// gatewayMeta embeds both a BoundAPIGateway and its corresponding APIGateway.
|
|
// This is used when binding routes to a gateway to ensure that a route's protocol (e.g. http)
|
|
// matches the protocol of the listener it wants to bind to. The binding modifies the
|
|
// "bound" gateway, but relies on the "gateway" to determine the protocol of the listener.
|
|
type gatewayMeta struct {
|
|
// BoundGateway is the bound-api-gateway config entry for a given gateway.
|
|
BoundGateway *structs.BoundAPIGatewayConfigEntry
|
|
// Gateway is the api-gateway config entry for the gateway.
|
|
Gateway *structs.APIGatewayConfigEntry
|
|
}
|
|
|
|
// updateRouteBinding takes a parent resource reference and a BoundRoute and
|
|
// modifies the listeners on the BoundAPIGateway config entry in GatewayMeta
|
|
// to reflect the binding of the route to the gateway.
|
|
//
|
|
// If the reference is not valid or the route's protocol does not match the
|
|
// targeted listener's protocol, a mapping of parent references to associated
|
|
// errors is returned.
|
|
func (g *gatewayMeta) updateRouteBinding(refs []structs.ResourceReference, route structs.BoundRoute) (bool, map[structs.ResourceReference]error) {
|
|
if g.BoundGateway == nil || g.Gateway == nil {
|
|
return false, nil
|
|
}
|
|
|
|
didUpdate := false
|
|
errors := make(map[structs.ResourceReference]error)
|
|
|
|
if len(g.BoundGateway.Listeners) == 0 {
|
|
for _, ref := range refs {
|
|
errors[ref] = fmt.Errorf("route cannot bind because gateway has no listeners")
|
|
}
|
|
return false, errors
|
|
}
|
|
|
|
for i, listener := range g.BoundGateway.Listeners {
|
|
// Unbind to handle any stale route references.
|
|
didUnbind := listener.UnbindRoute(route)
|
|
if didUnbind {
|
|
didUpdate = true
|
|
}
|
|
g.BoundGateway.Listeners[i] = listener
|
|
|
|
for _, ref := range refs {
|
|
didBind, err := g.bindRoute(ref, route)
|
|
if err != nil {
|
|
errors[ref] = err
|
|
}
|
|
if didBind {
|
|
didUpdate = true
|
|
}
|
|
}
|
|
}
|
|
|
|
return didUpdate, errors
|
|
}
|
|
|
|
// bindRoute takes a parent reference and a route and attempts to bind the route to the
|
|
// bound gateway in the gatewayMeta struct. It returns true if the route was bound and
|
|
// false if it was not. If the route fails to bind, an error is returned.
|
|
//
|
|
// Binding logic binds a route to one or more listeners on the Bound gateway.
|
|
// For a route to successfully bind it must:
|
|
// - have a parent reference to the gateway
|
|
// - have a parent reference with a section name matching the name of a listener
|
|
// on the gateway. If the section name is `""`, the route will be bound to all
|
|
// listeners on the gateway whose protocol matches the route's protocol.
|
|
// - have a protocol that matches the protocol of the listener it is being bound to.
|
|
func (g *gatewayMeta) bindRoute(ref structs.ResourceReference, route structs.BoundRoute) (bool, error) {
|
|
if g.BoundGateway == nil || g.Gateway == nil {
|
|
return false, fmt.Errorf("gateway cannot be found")
|
|
}
|
|
|
|
if ref.Kind != structs.APIGateway || g.Gateway.Name != ref.Name || !g.Gateway.EnterpriseMeta.IsSame(&ref.EnterpriseMeta) {
|
|
return false, nil
|
|
}
|
|
|
|
if len(g.BoundGateway.Listeners) == 0 {
|
|
return false, fmt.Errorf("route cannot bind because gateway has no listeners")
|
|
}
|
|
|
|
didBind := false
|
|
for _, listener := range g.Gateway.Listeners {
|
|
// A route with a section name of "" is bound to all listeners on the gateway.
|
|
if listener.Name != ref.SectionName && ref.SectionName != "" {
|
|
continue
|
|
}
|
|
|
|
if listener.Protocol == route.GetProtocol() {
|
|
i, boundListener := g.boundListenerByName(listener.Name)
|
|
if boundListener != nil && boundListener.BindRoute(route) {
|
|
didBind = true
|
|
g.BoundGateway.Listeners[i] = *boundListener
|
|
}
|
|
} else if ref.SectionName != "" {
|
|
// Failure to bind to a specific listener is an error
|
|
return false, fmt.Errorf("failed to bind route %s to gateway %s: listener %s is not a %s listener", route.GetName(), g.Gateway.Name, listener.Name, route.GetProtocol())
|
|
}
|
|
}
|
|
|
|
if !didBind {
|
|
return didBind, fmt.Errorf("failed to bind route %s to gateway %s: no valid listener has name '%s' and uses %s protocol", route.GetName(), g.Gateway.Name, ref.SectionName, route.GetProtocol())
|
|
}
|
|
|
|
return didBind, nil
|
|
}
|
|
|
|
// unbindRoute takes a route and unbinds it from all of the listeners on a gateway.
|
|
// It returns true if the route was unbound and false if it was not.
|
|
func (g *gatewayMeta) unbindRoute(route structs.BoundRoute) bool {
|
|
if g.BoundGateway == nil {
|
|
return false
|
|
}
|
|
|
|
didUnbind := false
|
|
for i, listener := range g.BoundGateway.Listeners {
|
|
if listener.UnbindRoute(route) {
|
|
didUnbind = true
|
|
g.BoundGateway.Listeners[i] = listener
|
|
}
|
|
}
|
|
|
|
return didUnbind
|
|
}
|
|
|
|
func (g *gatewayMeta) boundListenerByName(name string) (int, *structs.BoundAPIGatewayListener) {
|
|
for i, listener := range g.BoundGateway.Listeners {
|
|
if listener.Name == name {
|
|
return i, &listener
|
|
}
|
|
}
|
|
return -1, nil
|
|
}
|