mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 20:52:33 +02:00
smarter scheme and port detection
This commit is contained in:
parent
8e2cc56afb
commit
71e8e4a462
14 changed files with 294 additions and 132 deletions
|
@ -13,9 +13,10 @@ type ProxyProperties struct {
|
||||||
DockerHost string `yaml:"-" json:"docker_host"`
|
DockerHost string `yaml:"-" json:"docker_host"`
|
||||||
ContainerName string `yaml:"-" json:"container_name"`
|
ContainerName string `yaml:"-" json:"container_name"`
|
||||||
ImageName string `yaml:"-" json:"image_name"`
|
ImageName string `yaml:"-" json:"image_name"`
|
||||||
|
PublicPortMapping PortMapping `yaml:"-" json:"public_port_mapping"` // non-zero publicPort:types.Port
|
||||||
|
PrivatePortMapping PortMapping `yaml:"-" json:"private_port_mapping"` // privatePort:types.Port
|
||||||
Aliases []string `yaml:"-" json:"aliases"`
|
Aliases []string `yaml:"-" json:"aliases"`
|
||||||
IsExcluded bool `yaml:"-" json:"is_excluded"`
|
IsExcluded bool `yaml:"-" json:"is_excluded"`
|
||||||
FirstPort string `yaml:"-" json:"first_port"`
|
|
||||||
IdleTimeout string `yaml:"-" json:"idle_timeout"`
|
IdleTimeout string `yaml:"-" json:"idle_timeout"`
|
||||||
WakeTimeout string `yaml:"-" json:"wake_timeout"`
|
WakeTimeout string `yaml:"-" json:"wake_timeout"`
|
||||||
StopMethod string `yaml:"-" json:"stop_method"`
|
StopMethod string `yaml:"-" json:"stop_method"`
|
||||||
|
@ -29,15 +30,18 @@ type Container struct {
|
||||||
*ProxyProperties
|
*ProxyProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PortMapping = map[string]types.Port
|
||||||
|
|
||||||
func FromDocker(c *types.Container, dockerHost string) (res Container) {
|
func FromDocker(c *types.Container, dockerHost string) (res Container) {
|
||||||
res.Container = c
|
res.Container = c
|
||||||
res.ProxyProperties = &ProxyProperties{
|
res.ProxyProperties = &ProxyProperties{
|
||||||
DockerHost: dockerHost,
|
DockerHost: dockerHost,
|
||||||
ContainerName: res.getName(),
|
ContainerName: res.getName(),
|
||||||
ImageName: res.getImageName(),
|
ImageName: res.getImageName(),
|
||||||
|
PublicPortMapping: res.getPublicPortMapping(),
|
||||||
|
PrivatePortMapping: res.getPrivatePortMapping(),
|
||||||
Aliases: res.getAliases(),
|
Aliases: res.getAliases(),
|
||||||
IsExcluded: U.ParseBool(res.getDeleteLabel(LableExclude)),
|
IsExcluded: U.ParseBool(res.getDeleteLabel(LabelExclude)),
|
||||||
FirstPort: res.firstPortOrEmpty(),
|
|
||||||
IdleTimeout: res.getDeleteLabel(LabelIdleTimeout),
|
IdleTimeout: res.getDeleteLabel(LabelIdleTimeout),
|
||||||
WakeTimeout: res.getDeleteLabel(LabelWakeTimeout),
|
WakeTimeout: res.getDeleteLabel(LabelWakeTimeout),
|
||||||
StopMethod: res.getDeleteLabel(LabelStopMethod),
|
StopMethod: res.getDeleteLabel(LabelStopMethod),
|
||||||
|
@ -81,7 +85,7 @@ func (c Container) getDeleteLabel(label string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Container) getAliases() []string {
|
func (c Container) getAliases() []string {
|
||||||
if l := c.getDeleteLabel(LableAliases); l != "" {
|
if l := c.getDeleteLabel(LabelAliases); l != "" {
|
||||||
return U.CommaSeperatedList(l)
|
return U.CommaSeperatedList(l)
|
||||||
} else {
|
} else {
|
||||||
return []string{c.getName()}
|
return []string{c.getName()}
|
||||||
|
@ -98,14 +102,24 @@ func (c Container) getImageName() string {
|
||||||
return slashSep[len(slashSep)-1]
|
return slashSep[len(slashSep)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Container) firstPortOrEmpty() string {
|
func (c Container) getPublicPortMapping() PortMapping {
|
||||||
if len(c.Ports) == 0 {
|
res := make(PortMapping)
|
||||||
return ""
|
for _, v := range c.Ports {
|
||||||
|
if v.PublicPort == 0 {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
for _, p := range c.Ports {
|
res[fmt.Sprint(v.PublicPort)] = v
|
||||||
if p.PublicPort != 0 {
|
|
||||||
return fmt.Sprint(p.PublicPort)
|
|
||||||
}
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
|
func (c Container) getPrivatePortMapping() PortMapping {
|
||||||
|
res := make(PortMapping)
|
||||||
|
for _, v := range c.Ports {
|
||||||
|
if v.PublicPort == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res[fmt.Sprint(v.PrivatePort)] = v
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ package docker
|
||||||
const (
|
const (
|
||||||
WildcardAlias = "*"
|
WildcardAlias = "*"
|
||||||
|
|
||||||
LableAliases = NSProxy + ".aliases"
|
LabelAliases = NSProxy + ".aliases"
|
||||||
LableExclude = NSProxy + ".exclude"
|
LabelExclude = NSProxy + ".exclude"
|
||||||
LabelIdleTimeout = NSProxy + ".idle_timeout"
|
LabelIdleTimeout = NSProxy + ".idle_timeout"
|
||||||
LabelWakeTimeout = NSProxy + ".wake_timeout"
|
LabelWakeTimeout = NSProxy + ".wake_timeout"
|
||||||
LabelStopMethod = NSProxy + ".stop_method"
|
LabelStopMethod = NSProxy + ".stop_method"
|
||||||
|
|
|
@ -118,9 +118,9 @@ func (ne NestedError) With(s any) NestedError {
|
||||||
case string:
|
case string:
|
||||||
msg = ss
|
msg = ss
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
msg = ss.String()
|
return ne.append(ss.String())
|
||||||
default:
|
default:
|
||||||
msg = fmt.Sprint(s)
|
return ne.append(fmt.Sprint(s))
|
||||||
}
|
}
|
||||||
return ne.withError(From(errors.New(msg)))
|
return ne.withError(From(errors.New(msg)))
|
||||||
}
|
}
|
||||||
|
@ -201,6 +201,14 @@ func (ne NestedError) withError(err NestedError) NestedError {
|
||||||
return ne
|
return ne
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ne NestedError) append(msg string) NestedError {
|
||||||
|
if ne == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ne.err = fmt.Errorf("%w %s", ne.err, msg)
|
||||||
|
return ne
|
||||||
|
}
|
||||||
|
|
||||||
func (ne NestedError) writeToSB(sb *strings.Builder, level int, prefix string) {
|
func (ne NestedError) writeToSB(sb *strings.Builder, level int, prefix string) {
|
||||||
for i := 0; i < level; i++ {
|
for i := 0; i < level; i++ {
|
||||||
sb.WriteString(" ")
|
sb.WriteString(" ")
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -53,11 +54,17 @@ func (e *RawEntry) FillMissingFields() bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Port == "" {
|
if e.PublicPortMapping != nil {
|
||||||
if e.FirstPort == "" {
|
if _, ok := e.PublicPortMapping[e.Port]; !ok { // port is not exposed, but specified
|
||||||
|
// try to fallback to first public port
|
||||||
|
if len(e.PublicPortMapping) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
e.Port = e.FirstPort
|
for _, p := range e.PublicPortMapping {
|
||||||
|
e.Port = fmt.Sprint(p.PublicPort)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Scheme == "" {
|
if e.Scheme == "" {
|
||||||
|
@ -69,8 +76,19 @@ func (e *RawEntry) FillMissingFields() bool {
|
||||||
e.Scheme = "http"
|
e.Scheme = "http"
|
||||||
} else if e.Port == "443" {
|
} else if e.Port == "443" {
|
||||||
e.Scheme = "https"
|
e.Scheme = "https"
|
||||||
} else if isDocker && e.Port == "" {
|
} else if isDocker {
|
||||||
|
if e.Port == "" {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
if p, ok := e.PublicPortMapping[e.Port]; ok {
|
||||||
|
if p.Type == "udp" {
|
||||||
|
e.Scheme = "udp"
|
||||||
|
} else {
|
||||||
|
e.Scheme = "http"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
e.Scheme = "http"
|
e.Scheme = "http"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
package proxy
|
|
||||||
|
|
||||||
const (
|
|
||||||
StreamType_UDP string = "udp"
|
|
||||||
StreamType_TCP string = "tcp"
|
|
||||||
// StreamType_UDP_TCP Scheme = "udp-tcp"
|
|
||||||
// StreamType_TCP_UDP Scheme = "tcp-udp"
|
|
||||||
// StreamType_TLS Scheme = "tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// TODO: support "tcp-udp", "udp-tcp", etc.
|
|
||||||
StreamSchemes = []string{StreamType_TCP, StreamType_UDP}
|
|
||||||
HTTPSchemes = []string{"http", "https"}
|
|
||||||
ValidSchemes = append(StreamSchemes, HTTPSchemes...)
|
|
||||||
)
|
|
|
@ -28,6 +28,10 @@ func (p Port) inBound() bool {
|
||||||
return p >= MinPort && p <= MaxPort
|
return p >= MinPort && p <= MaxPort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Port) String() string {
|
||||||
|
return strconv.Itoa(int(p))
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MinPort = 0
|
MinPort = 0
|
||||||
MaxPort = 65535
|
MaxPort = 65535
|
||||||
|
|
|
@ -37,7 +37,7 @@ func ValidateStreamPort(p string) (StreamPort, E.NestedError) {
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
proxyPort, err = parseNameToPort(split[1])
|
proxyPort, err = parseNameToPort(split[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrStreamPort, err
|
return ErrStreamPort, E.Invalid("stream port", p).With(proxyPort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
U "github.com/yusing/go-proxy/utils/testing"
|
. "github.com/yusing/go-proxy/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var validPorts = []string{
|
var validPorts = []string{
|
||||||
|
@ -35,14 +35,14 @@ var outOfRangePorts = []string{
|
||||||
func TestStreamPort(t *testing.T) {
|
func TestStreamPort(t *testing.T) {
|
||||||
for _, port := range validPorts {
|
for _, port := range validPorts {
|
||||||
_, err := ValidateStreamPort(port)
|
_, err := ValidateStreamPort(port)
|
||||||
U.ExpectNoError(t, err.Error())
|
ExpectNoError(t, err.Error())
|
||||||
}
|
}
|
||||||
for _, port := range invalidPorts {
|
for _, port := range invalidPorts {
|
||||||
_, err := ValidateStreamPort(port)
|
_, err := ValidateStreamPort(port)
|
||||||
U.ExpectError2(t, port, E.ErrInvalid, err.Error())
|
ExpectError2(t, port, E.ErrInvalid, err.Error())
|
||||||
}
|
}
|
||||||
for _, port := range outOfRangePorts {
|
for _, port := range outOfRangePorts {
|
||||||
_, err := ValidateStreamPort(port)
|
_, err := ValidateStreamPort(port)
|
||||||
U.ExpectError2(t, port, E.ErrOutOfRange, err.Error())
|
ExpectError2(t, port, E.ErrOutOfRange, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,31 +137,29 @@ func (p *DockerProvider) entriesFromContainerLabels(container D.Container) (M.Ra
|
||||||
errors.Add(p.applyLabel(container, entries, key, val))
|
errors.Add(p.applyLabel(container, entries, key, val))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// selecting correct host port
|
||||||
|
replacePrivPorts := func() {
|
||||||
|
if container.HostConfig.NetworkMode != "host" {
|
||||||
|
entries.RangeAll(func(_ string, entry *M.RawEntry) {
|
||||||
|
entryPortSplit := strings.Split(entry.Port, ":")
|
||||||
|
n := len(entryPortSplit)
|
||||||
|
// if the port matches the proxy port, replace it with the public port
|
||||||
|
if p, ok := container.PrivatePortMapping[entryPortSplit[n-1]]; ok {
|
||||||
|
entryPortSplit[n-1] = fmt.Sprint(p.PublicPort)
|
||||||
|
entry.Port = strings.Join(entryPortSplit, ":")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replacePrivPorts()
|
||||||
|
|
||||||
// remove all entries that failed to fill in missing fields
|
// remove all entries that failed to fill in missing fields
|
||||||
entries.RemoveAll(func(re *M.RawEntry) bool {
|
entries.RemoveAll(func(re *M.RawEntry) bool {
|
||||||
return !re.FillMissingFields()
|
return !re.FillMissingFields()
|
||||||
})
|
})
|
||||||
|
|
||||||
// selecting correct host port
|
// do it again since the port may got filled in
|
||||||
if container.HostConfig.NetworkMode != "host" {
|
replacePrivPorts()
|
||||||
for _, a := range container.Aliases {
|
|
||||||
entry, ok := entries.Load(a)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, p := range container.Ports {
|
|
||||||
containerPort := strconv.Itoa(int(p.PrivatePort))
|
|
||||||
publicPort := strconv.Itoa(int(p.PublicPort))
|
|
||||||
entryPortSplit := strings.Split(entry.Port, ":")
|
|
||||||
n := len(entryPortSplit)
|
|
||||||
if entryPortSplit[n-1] == containerPort {
|
|
||||||
entryPortSplit[n-1] = publicPort
|
|
||||||
entry.Port = strings.Join(entryPortSplit, ":")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries, errors.Build().Subject(container.ContainerName)
|
return entries, errors.Build().Subject(container.ContainerName)
|
||||||
}
|
}
|
||||||
|
@ -193,7 +191,7 @@ func (p *DockerProvider) applyLabel(container D.Container, entries M.RawEntries,
|
||||||
return ref
|
return ref
|
||||||
}
|
}
|
||||||
if index < 1 || index > len(container.Aliases) {
|
if index < 1 || index > len(container.Aliases) {
|
||||||
refErr.Add(E.Invalid("index", ref).Extraf("index out of range"))
|
refErr.Add(E.OutOfRange("index", ref))
|
||||||
return ref
|
return ref
|
||||||
}
|
}
|
||||||
return container.Aliases[index-1]
|
return container.Aliases[index-1]
|
||||||
|
|
|
@ -8,15 +8,12 @@ import (
|
||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/common"
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
F "github.com/yusing/go-proxy/utils/functional"
|
P "github.com/yusing/go-proxy/proxy"
|
||||||
|
T "github.com/yusing/go-proxy/proxy/fields"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/utils/testing"
|
. "github.com/yusing/go-proxy/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func get[KT comparable, VT any](m F.Map[KT, VT], key KT) VT {
|
|
||||||
v, _ := m.Load(key)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
var dummyNames = []string{"/a"}
|
var dummyNames = []string{"/a"}
|
||||||
|
|
||||||
func TestApplyLabelFieldValidity(t *testing.T) {
|
func TestApplyLabelFieldValidity(t *testing.T) {
|
||||||
|
@ -48,10 +45,10 @@ X_Custom_Header2: value3
|
||||||
"X-Custom-Header2",
|
"X-Custom-Header2",
|
||||||
}
|
}
|
||||||
var p DockerProvider
|
var p DockerProvider
|
||||||
var c = D.FromDocker(&types.Container{
|
entries, err := p.entriesFromContainerLabels(D.FromDocker(&types.Container{
|
||||||
Names: dummyNames,
|
Names: dummyNames,
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
D.LableAliases: "a,b",
|
D.LabelAliases: "a,b",
|
||||||
D.LabelIdleTimeout: common.IdleTimeoutDefault,
|
D.LabelIdleTimeout: common.IdleTimeoutDefault,
|
||||||
D.LabelStopMethod: common.StopMethodDefault,
|
D.LabelStopMethod: common.StopMethodDefault,
|
||||||
D.LabelStopSignal: "SIGTERM",
|
D.LabelStopSignal: "SIGTERM",
|
||||||
|
@ -65,11 +62,16 @@ X_Custom_Header2: value3
|
||||||
"proxy.a.path_patterns": pathPatterns,
|
"proxy.a.path_patterns": pathPatterns,
|
||||||
"proxy.a.set_headers": setHeaders,
|
"proxy.a.set_headers": setHeaders,
|
||||||
"proxy.a.hide_headers": hideHeaders,
|
"proxy.a.hide_headers": hideHeaders,
|
||||||
}}, "")
|
},
|
||||||
entries, err := p.entriesFromContainerLabels(c)
|
Ports: []types.Port{
|
||||||
|
{Type: "tcp", PrivatePort: 4567, PublicPort: 8888},
|
||||||
|
}}, ""))
|
||||||
ExpectNoError(t, err.Error())
|
ExpectNoError(t, err.Error())
|
||||||
a := get(entries, "a")
|
|
||||||
b := get(entries, "b")
|
a, ok := entries.Load("a")
|
||||||
|
ExpectTrue(t, ok)
|
||||||
|
b, ok := entries.Load("b")
|
||||||
|
ExpectTrue(t, ok)
|
||||||
|
|
||||||
ExpectEqual(t, a.Scheme, "https")
|
ExpectEqual(t, a.Scheme, "https")
|
||||||
ExpectEqual(t, b.Scheme, "https")
|
ExpectEqual(t, b.Scheme, "https")
|
||||||
|
@ -77,8 +79,8 @@ X_Custom_Header2: value3
|
||||||
ExpectEqual(t, a.Host, "app")
|
ExpectEqual(t, a.Host, "app")
|
||||||
ExpectEqual(t, b.Host, "app")
|
ExpectEqual(t, b.Host, "app")
|
||||||
|
|
||||||
ExpectEqual(t, a.Port, "4567")
|
ExpectEqual(t, a.Port, "8888")
|
||||||
ExpectEqual(t, b.Port, "4567")
|
ExpectEqual(t, b.Port, "8888")
|
||||||
|
|
||||||
ExpectTrue(t, a.NoTLSVerify)
|
ExpectTrue(t, a.NoTLSVerify)
|
||||||
ExpectTrue(t, b.NoTLSVerify)
|
ExpectTrue(t, b.NoTLSVerify)
|
||||||
|
@ -110,38 +112,68 @@ X_Custom_Header2: value3
|
||||||
|
|
||||||
func TestApplyLabel(t *testing.T) {
|
func TestApplyLabel(t *testing.T) {
|
||||||
var p DockerProvider
|
var p DockerProvider
|
||||||
var c = D.FromDocker(&types.Container{
|
entries, err := p.entriesFromContainerLabels(D.FromDocker(&types.Container{
|
||||||
Names: dummyNames,
|
Names: dummyNames,
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
D.LableAliases: "a,b,c",
|
D.LabelAliases: "a,b,c",
|
||||||
"proxy.a.no_tls_verify": "true",
|
"proxy.a.no_tls_verify": "true",
|
||||||
"proxy.a.port": "3333",
|
"proxy.a.port": "3333",
|
||||||
"proxy.b.port": "1234",
|
"proxy.b.port": "1234",
|
||||||
"proxy.c.scheme": "https",
|
"proxy.c.scheme": "https",
|
||||||
}}, "")
|
},
|
||||||
entries, err := p.entriesFromContainerLabels(c)
|
Ports: []types.Port{
|
||||||
|
{Type: "tcp", PrivatePort: 3333, PublicPort: 1111},
|
||||||
|
{Type: "tcp", PrivatePort: 4444, PublicPort: 1234},
|
||||||
|
}}, "",
|
||||||
|
))
|
||||||
|
a, ok := entries.Load("a")
|
||||||
|
ExpectTrue(t, ok)
|
||||||
|
b, ok := entries.Load("b")
|
||||||
|
ExpectTrue(t, ok)
|
||||||
|
c, ok := entries.Load("c")
|
||||||
|
ExpectTrue(t, ok)
|
||||||
|
|
||||||
ExpectNoError(t, err.Error())
|
ExpectNoError(t, err.Error())
|
||||||
ExpectEqual(t, get(entries, "a").NoTLSVerify, true)
|
ExpectEqual(t, a.Scheme, "http")
|
||||||
ExpectEqual(t, get(entries, "b").Port, "1234")
|
ExpectEqual(t, a.Port, "1111")
|
||||||
ExpectEqual(t, get(entries, "c").Scheme, "https")
|
ExpectEqual(t, a.NoTLSVerify, true)
|
||||||
|
ExpectEqual(t, b.Scheme, "http")
|
||||||
|
ExpectEqual(t, b.Port, "1234")
|
||||||
|
ExpectEqual(t, c.Scheme, "https")
|
||||||
|
ExpectEqual(t, c.Port, "1111")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyLabelWithRef(t *testing.T) {
|
func TestApplyLabelWithRef(t *testing.T) {
|
||||||
var p DockerProvider
|
var p DockerProvider
|
||||||
var c = D.FromDocker(&types.Container{
|
entries, err := p.entriesFromContainerLabels(D.FromDocker(&types.Container{
|
||||||
Names: dummyNames,
|
Names: dummyNames,
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
D.LableAliases: "a,b,c",
|
D.LabelAliases: "a,b,c",
|
||||||
"proxy.$1.host": "localhost",
|
"proxy.$1.host": "localhost",
|
||||||
|
"proxy.*.port": "1111",
|
||||||
"proxy.$1.port": "4444",
|
"proxy.$1.port": "4444",
|
||||||
"proxy.$2.port": "1234",
|
"proxy.$2.port": "9999",
|
||||||
"proxy.$3.scheme": "https",
|
"proxy.$3.scheme": "https",
|
||||||
}}, "")
|
},
|
||||||
entries, err := p.entriesFromContainerLabels(c)
|
Ports: []types.Port{
|
||||||
|
{Type: "tcp", PrivatePort: 3333, PublicPort: 9999},
|
||||||
|
{Type: "tcp", PrivatePort: 4444, PublicPort: 5555},
|
||||||
|
{Type: "tcp", PrivatePort: 1111, PublicPort: 2222},
|
||||||
|
}}, ""))
|
||||||
|
a, ok := entries.Load("a")
|
||||||
|
ExpectTrue(t, ok)
|
||||||
|
b, ok := entries.Load("b")
|
||||||
|
ExpectTrue(t, ok)
|
||||||
|
c, ok := entries.Load("c")
|
||||||
|
ExpectTrue(t, ok)
|
||||||
|
|
||||||
ExpectNoError(t, err.Error())
|
ExpectNoError(t, err.Error())
|
||||||
ExpectEqual(t, get(entries, "a").Host, "localhost")
|
ExpectEqual(t, a.Scheme, "http")
|
||||||
ExpectEqual(t, get(entries, "b").Port, "1234")
|
ExpectEqual(t, a.Host, "localhost")
|
||||||
ExpectEqual(t, get(entries, "c").Scheme, "https")
|
ExpectEqual(t, a.Port, "5555")
|
||||||
|
ExpectEqual(t, b.Port, "9999")
|
||||||
|
ExpectEqual(t, c.Scheme, "https")
|
||||||
|
ExpectEqual(t, c.Port, "2222")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyLabelWithRefIndexError(t *testing.T) {
|
func TestApplyLabelWithRefIndexError(t *testing.T) {
|
||||||
|
@ -149,21 +181,110 @@ func TestApplyLabelWithRefIndexError(t *testing.T) {
|
||||||
var c = D.FromDocker(&types.Container{
|
var c = D.FromDocker(&types.Container{
|
||||||
Names: dummyNames,
|
Names: dummyNames,
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
D.LableAliases: "a,b",
|
D.LabelAliases: "a,b",
|
||||||
"proxy.$1.host": "localhost",
|
"proxy.$1.host": "localhost",
|
||||||
"proxy.$4.scheme": "https",
|
"proxy.$4.scheme": "https",
|
||||||
}}, "")
|
}}, "")
|
||||||
_, err := p.entriesFromContainerLabels(c)
|
_, err := p.entriesFromContainerLabels(c)
|
||||||
ExpectError(t, E.ErrInvalid, err.Error())
|
ExpectError(t, E.ErrOutOfRange, err.Error())
|
||||||
ExpectTrue(t, strings.Contains(err.String(), "index out of range"))
|
ExpectTrue(t, strings.Contains(err.String(), "index out of range"))
|
||||||
|
|
||||||
c = D.FromDocker(&types.Container{
|
_, err = p.entriesFromContainerLabels(D.FromDocker(&types.Container{
|
||||||
Names: dummyNames,
|
Names: dummyNames,
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
D.LableAliases: "a,b",
|
D.LabelAliases: "a,b",
|
||||||
"proxy.$0.host": "localhost",
|
"proxy.$0.host": "localhost",
|
||||||
}}, "")
|
}}, ""))
|
||||||
_, err = p.entriesFromContainerLabels(c)
|
ExpectError(t, E.ErrOutOfRange, err.Error())
|
||||||
ExpectError(t, E.ErrInvalid, err.Error())
|
|
||||||
ExpectTrue(t, strings.Contains(err.String(), "index out of range"))
|
ExpectTrue(t, strings.Contains(err.String(), "index out of range"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStreamDefaultValues(t *testing.T) {
|
||||||
|
var p DockerProvider
|
||||||
|
var c = D.FromDocker(&types.Container{
|
||||||
|
Names: dummyNames,
|
||||||
|
Labels: map[string]string{
|
||||||
|
D.LabelAliases: "a",
|
||||||
|
"proxy.*.no_tls_verify": "true",
|
||||||
|
},
|
||||||
|
Ports: []types.Port{
|
||||||
|
{Type: "udp", PrivatePort: 1234, PublicPort: 5678},
|
||||||
|
}}, "",
|
||||||
|
)
|
||||||
|
entries, err := p.entriesFromContainerLabels(c)
|
||||||
|
ExpectNoError(t, err.Error())
|
||||||
|
|
||||||
|
raw, ok := entries.Load("a")
|
||||||
|
ExpectTrue(t, ok)
|
||||||
|
|
||||||
|
entry, err := P.ValidateEntry(raw)
|
||||||
|
ExpectNoError(t, err.Error())
|
||||||
|
|
||||||
|
a := ExpectType[*P.StreamEntry](t, entry)
|
||||||
|
ExpectEqual(t, a.Scheme.ListeningScheme, T.Scheme("udp"))
|
||||||
|
ExpectEqual(t, a.Scheme.ProxyScheme, T.Scheme("udp"))
|
||||||
|
ExpectEqual(t, a.Port.ListeningPort, 0)
|
||||||
|
ExpectEqual(t, a.Port.ProxyPort, 5678)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExplicitExclude(t *testing.T) {
|
||||||
|
var p DockerProvider
|
||||||
|
entries, err := p.entriesFromContainerLabels(D.FromDocker(&types.Container{
|
||||||
|
Names: dummyNames,
|
||||||
|
Labels: map[string]string{
|
||||||
|
D.LabelAliases: "a",
|
||||||
|
D.LabelExclude: "true",
|
||||||
|
"proxy.a.no_tls_verify": "true",
|
||||||
|
}}, ""))
|
||||||
|
ExpectNoError(t, err.Error())
|
||||||
|
|
||||||
|
_, ok := entries.Load("a")
|
||||||
|
ExpectFalse(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImplicitExclude(t *testing.T) {
|
||||||
|
var p DockerProvider
|
||||||
|
entries, err := p.entriesFromContainerLabels(D.FromDocker(&types.Container{
|
||||||
|
Names: dummyNames,
|
||||||
|
Labels: map[string]string{
|
||||||
|
D.LabelAliases: "a",
|
||||||
|
"proxy.a.no_tls_verify": "true",
|
||||||
|
}}, ""))
|
||||||
|
ExpectNoError(t, err.Error())
|
||||||
|
|
||||||
|
_, ok := entries.Load("a")
|
||||||
|
ExpectFalse(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImplicitExcludeNoExposedPort(t *testing.T) {
|
||||||
|
var p DockerProvider
|
||||||
|
entries, err := p.entriesFromContainerLabels(D.FromDocker(&types.Container{
|
||||||
|
Image: "redis",
|
||||||
|
Names: []string{"redis"},
|
||||||
|
Ports: []types.Port{
|
||||||
|
{Type: "tcp", PrivatePort: 6379, PublicPort: 0}, // not exposed
|
||||||
|
},
|
||||||
|
}, ""))
|
||||||
|
ExpectNoError(t, err.Error())
|
||||||
|
|
||||||
|
_, ok := entries.Load("redis")
|
||||||
|
ExpectFalse(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExcludeNonExposedPort(t *testing.T) {
|
||||||
|
var p DockerProvider
|
||||||
|
entries, err := p.entriesFromContainerLabels(D.FromDocker(&types.Container{
|
||||||
|
Image: "redis",
|
||||||
|
Names: []string{"redis"},
|
||||||
|
Ports: []types.Port{
|
||||||
|
{Type: "tcp", PrivatePort: 6379, PublicPort: 0}, // not exposed
|
||||||
|
},
|
||||||
|
Labels: map[string]string{
|
||||||
|
"proxy.redis.port": "6379:6379", // should be excluded even specified
|
||||||
|
},
|
||||||
|
}, ""))
|
||||||
|
ExpectNoError(t, err.Error())
|
||||||
|
|
||||||
|
_, ok := entries.Load("redis")
|
||||||
|
ExpectFalse(t, ok)
|
||||||
|
}
|
||||||
|
|
|
@ -18,13 +18,13 @@ type (
|
||||||
URL() *url.URL
|
URL() *url.URL
|
||||||
}
|
}
|
||||||
Routes = F.Map[string, Route]
|
Routes = F.Map[string, Route]
|
||||||
RouteType string
|
|
||||||
|
|
||||||
RouteImpl interface {
|
RouteImpl interface {
|
||||||
Start() E.NestedError
|
Start() E.NestedError
|
||||||
Stop() E.NestedError
|
Stop() E.NestedError
|
||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
|
RouteType string
|
||||||
route struct {
|
route struct {
|
||||||
RouteImpl
|
RouteImpl
|
||||||
type_ RouteType
|
type_ RouteType
|
||||||
|
@ -58,7 +58,10 @@ func NewRoute(en *M.RawEntry) (Route, E.NestedError) {
|
||||||
default:
|
default:
|
||||||
panic("bug: should not reach here")
|
panic("bug: should not reach here")
|
||||||
}
|
}
|
||||||
return &route{RouteImpl: rt.(RouteImpl), entry: en, type_: t}, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &route{RouteImpl: rt.(RouteImpl), entry: en, type_: t}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *route) Entry() *M.RawEntry {
|
func (rt *route) Entry() *M.RawEntry {
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type StreamRoute struct {
|
type StreamRoute struct {
|
||||||
P.StreamEntry
|
*P.StreamEntry
|
||||||
StreamImpl `json:"-"`
|
StreamImpl `json:"-"`
|
||||||
|
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
@ -31,6 +31,7 @@ type StreamImpl interface {
|
||||||
Accept() (any, error)
|
Accept() (any, error)
|
||||||
Handle(any) error
|
Handle(any) error
|
||||||
CloseListeners()
|
CloseListeners()
|
||||||
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStreamRoute(entry *P.StreamEntry) (*StreamRoute, E.NestedError) {
|
func NewStreamRoute(entry *P.StreamEntry) (*StreamRoute, E.NestedError) {
|
||||||
|
@ -39,7 +40,7 @@ func NewStreamRoute(entry *P.StreamEntry) (*StreamRoute, E.NestedError) {
|
||||||
return nil, E.Unsupported("scheme", fmt.Sprintf("%v -> %v", entry.Scheme.ListeningScheme, entry.Scheme.ProxyScheme))
|
return nil, E.Unsupported("scheme", fmt.Sprintf("%v -> %v", entry.Scheme.ListeningScheme, entry.Scheme.ProxyScheme))
|
||||||
}
|
}
|
||||||
base := &StreamRoute{
|
base := &StreamRoute{
|
||||||
StreamEntry: *entry,
|
StreamEntry: entry,
|
||||||
connCh: make(chan any, 100),
|
connCh: make(chan any, 100),
|
||||||
}
|
}
|
||||||
if entry.Scheme.ListeningScheme.IsTCP() {
|
if entry.Scheme.ListeningScheme.IsTCP() {
|
||||||
|
@ -64,6 +65,7 @@ func (r *StreamRoute) Start() E.NestedError {
|
||||||
if err := r.Setup(); err != nil {
|
if err := r.Setup(); err != nil {
|
||||||
return E.FailWith("setup", err)
|
return E.FailWith("setup", err)
|
||||||
}
|
}
|
||||||
|
r.l.Infof("listening on port %d", r.Port.ListeningPort)
|
||||||
r.started.Store(true)
|
r.started.Store(true)
|
||||||
r.wg.Add(2)
|
r.wg.Add(2)
|
||||||
go r.grAcceptConnections()
|
go r.grAcceptConnections()
|
||||||
|
|
|
@ -52,10 +52,11 @@ func (route *UDPRoute) Setup() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
//! this read the allocated listeningPort from orginal ':0'
|
//! this read the allocated listeningPort from orginal ':0'
|
||||||
route.Port.ListeningPort = T.Port(laddr.Port)
|
route.Port.ListeningPort = T.Port(source.LocalAddr().(*net.UDPAddr).Port)
|
||||||
|
|
||||||
route.listeningConn = source
|
route.listeningConn = source
|
||||||
route.targetAddr = raddr
|
route.targetAddr = raddr
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ func ExpectNoError(t *testing.T, err error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if err != nil && !reflect.ValueOf(err).IsNil() {
|
if err != nil && !reflect.ValueOf(err).IsNil() {
|
||||||
t.Errorf("expected err=nil, got %s", err.Error())
|
t.Errorf("expected err=nil, got %s", err.Error())
|
||||||
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ func ExpectError(t *testing.T, expected error, err error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if !errors.Is(err, expected) {
|
if !errors.Is(err, expected) {
|
||||||
t.Errorf("expected err %s, got %s", expected.Error(), err.Error())
|
t.Errorf("expected err %s, got %s", expected.Error(), err.Error())
|
||||||
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +26,7 @@ func ExpectError2(t *testing.T, input any, expected error, err error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if !errors.Is(err, expected) {
|
if !errors.Is(err, expected) {
|
||||||
t.Errorf("%v: expected err %s, got %s", input, expected.Error(), err.Error())
|
t.Errorf("%v: expected err %s, got %s", input, expected.Error(), err.Error())
|
||||||
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +34,7 @@ func ExpectEqual[T comparable](t *testing.T, got T, want T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf("expected:\n%v, got\n%v", want, got)
|
t.Errorf("expected:\n%v, got\n%v", want, got)
|
||||||
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,29 +42,34 @@ func ExpectDeepEqual[T any](t *testing.T, got T, want T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if !reflect.DeepEqual(got, want) {
|
if !reflect.DeepEqual(got, want) {
|
||||||
t.Errorf("expected:\n%v, got\n%v", want, got)
|
t.Errorf("expected:\n%v, got\n%v", want, got)
|
||||||
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectTrue(t *testing.T, got bool) {
|
func ExpectTrue(t *testing.T, got bool) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if !got {
|
if !got {
|
||||||
t.Errorf("expected true, got false")
|
t.Error("expected true")
|
||||||
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectFalse(t *testing.T, got bool) {
|
func ExpectFalse(t *testing.T, got bool) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if got {
|
if got {
|
||||||
t.Errorf("expected false, got true")
|
t.Error("expected false")
|
||||||
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectType[T any](t *testing.T, got any) T {
|
func ExpectType[T any](t *testing.T, got any) (_ T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
tExpect := reflect.TypeFor[T]()
|
tExpect := reflect.TypeFor[T]()
|
||||||
_, ok := got.(T)
|
_, ok := got.(T)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("expected type %s, got %T", tExpect, got)
|
t.Fatalf("expected type %s, got %s", tExpect, reflect.TypeOf(got).Elem())
|
||||||
|
t.FailNow()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return got.(T)
|
return got.(T)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue