@ -9,11 +9,8 @@
package egressservices
package egressservices
import (
import (
"encoding"
"encoding/json"
"fmt"
"net/netip"
"net/netip"
"strconv"
"strings"
)
)
// KeyEgressServices is name of the proxy state Secret field that contains the
// KeyEgressServices is name of the proxy state Secret field that contains the
@ -31,7 +28,7 @@ type Config struct {
// should be proxied.
// should be proxied.
TailnetTarget TailnetTarget ` json:"tailnetTarget" `
TailnetTarget TailnetTarget ` json:"tailnetTarget" `
// Ports contains mappings for ports that can be accessed on the tailnet target.
// Ports contains mappings for ports that can be accessed on the tailnet target.
Ports map [ PortMap ] struct{ } ` json:"ports" `
Ports PortMap s ` json:"ports" `
}
}
// TailnetTarget is the tailnet target to which traffic for the egress service
// TailnetTarget is the tailnet target to which traffic for the egress service
@ -52,35 +49,38 @@ type PortMap struct {
TargetPort uint16 ` json:"targetPort" `
TargetPort uint16 ` json:"targetPort" `
}
}
// PortMap is used as a Config.Ports map key. Config needs to be serialized/deserialized to/from JSON. JSON only
type PortMaps map [ PortMap ] struct { }
// supports string map keys, so we need to implement TextMarshaler/TextUnmarshaler to convert PortMap to string and
// back.
// PortMaps is a list of PortMap structs, however, we want to use it as a set
var _ encoding . TextMarshaler = PortMap { }
// with efficient lookups in code. It implements custom JSON marshalling
var _ encoding . TextUnmarshaler = & PortMap { }
// methods to convert between being a list in JSON and a set (map with empty
// values) in code.
func ( pm * PortMap ) UnmarshalText ( t [ ] byte ) error {
var _ json . Marshaler = & PortMaps { }
tt := string ( t )
var _ json . Marshaler = PortMaps { }
ss := strings . Split ( tt , ":" )
var _ json . Unmarshaler = & PortMaps { }
if len ( ss ) != 3 {
return fmt . Errorf ( "error unmarshalling portmap from JSON, wants a portmap in form <protocol>:<matchPort>:<targetPor>, got %q" , tt )
func ( p * PortMaps ) UnmarshalJSON ( data [ ] byte ) error {
}
* p = make ( map [ PortMap ] struct { } )
pm . Protocol = ss [ 0 ]
matchPort , err := strconv . ParseUint ( ss [ 1 ] , 10 , 16 )
var l [ ] PortMap
if err != nil {
if err := json . Unmarshal ( data , & l ) ; err != nil {
return fmt. Errorf ( " error converting match port %q to uint16: %w", ss [ 1 ] , err )
return err
}
}
pm . MatchPort = uint16 ( matchPort )
targetPort , err := strconv . ParseUint ( ss [ 2 ] , 10 , 16 )
for _ , pm := range l {
if err != nil {
( * p ) [ pm ] = struct { } { }
return fmt . Errorf ( "error converting target port %q to uint16: %w" , ss [ 2 ] , err )
}
}
pm . TargetPort = uint16 ( targetPort )
return nil
return nil
}
}
func ( pm PortMap ) MarshalText ( ) ( [ ] byte , error ) {
func ( p PortMaps ) MarshalJSON ( ) ( [ ] byte , error ) {
s := fmt . Sprintf ( "%s:%d:%d" , pm . Protocol , pm . MatchPort , pm . TargetPort )
l := make ( [ ] PortMap , 0 , len ( p ) )
return [ ] byte ( s ) , nil
for pm := range p {
l = append ( l , pm )
}
return json . Marshal ( l )
}
}
// Status represents the currently configured firewall rules for all egress
// Status represents the currently configured firewall rules for all egress
@ -94,7 +94,7 @@ type Status struct {
// ServiceStatus is the currently configured firewall rules for an egress
// ServiceStatus is the currently configured firewall rules for an egress
// service.
// service.
type ServiceStatus struct {
type ServiceStatus struct {
Ports map [ PortMap ] struct{ } ` json:"ports" `
Ports PortMap s ` json:"ports" `
// TailnetTargetIPs are the tailnet target IPs that were used to
// TailnetTargetIPs are the tailnet target IPs that were used to
// configure these firewall rules. For a TailnetTarget with IP set, this
// configure these firewall rules. For a TailnetTarget with IP set, this
// is the same as IP.
// is the same as IP.