mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-14 06:13:33 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d10d0e49fa | ||
|
|
dc3575c8fd | ||
|
|
17115cfb0b | ||
|
|
498082f7e5 | ||
|
|
99216ffe59 | ||
|
|
f426dbc9cf | ||
|
|
1c611cc9b9 | ||
|
|
dc43e26770 |
2
Makefile
2
Makefile
@@ -1,6 +1,6 @@
|
||||
.PHONY: all build up quick-restart restart logs get udp-server
|
||||
|
||||
all: build quick-restart logs
|
||||
all: debug
|
||||
|
||||
setup:
|
||||
mkdir -p config certs
|
||||
|
||||
10
README.md
10
README.md
@@ -49,11 +49,17 @@ A lightweight, easy-to-use, and [performant](docs/benchmark_result.md) reverse p
|
||||
|
||||
### Setup
|
||||
|
||||
1. Pull docker image `docker pull ghcr.io/yusing/go-proxy:latest`
|
||||
1. Pull docker image
|
||||
|
||||
```shell
|
||||
docker pull ghcr.io/yusing/go-proxy:latest
|
||||
```
|
||||
|
||||
2. Create new directory, `cd` into it, then run setup
|
||||
|
||||
`docker run --rm -v .:/setup ghcr.io/yusing/go-proxy /app/go-proxy setup`
|
||||
```shell
|
||||
docker run --rm -v .:/setup ghcr.io/yusing/go-proxy /app/go-proxy setup
|
||||
```
|
||||
|
||||
3. Setup DNS Records point to machine which runs `go-proxy`, e.g.
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
| `proxy.aliases` | comma separated aliases for subdomain and label matching | `gitlab,gitlab-reg,gitlab-ssh` | `container_name` | any |
|
||||
| `proxy.exclude` | to be excluded from `go-proxy` | | false | boolean |
|
||||
| `proxy.idle_timeout` | time for idle (no traffic) before put it into sleep **(http/s only)**<br> _**NOTE: idlewatcher will only be enabled containers that has non-empty `idle_timeout`**_ | `1h` | empty or `0` **(disabled)** | `number[unit]...`, e.g. `1m30s` |
|
||||
| `proxy.wake_timeout` | time to wait for target site to be ready | | `10s` | `number[unit]...` |
|
||||
| `proxy.wake_timeout` | time to wait for target site to be ready | | `30s` | `number[unit]...` |
|
||||
| `proxy.stop_method` | method to stop after `idle_timeout` | | `stop` | `stop`, `pause`, `kill` |
|
||||
| `proxy.stop_timeout` | time to wait for stop command | | `10s` | `number[unit]...` |
|
||||
| `proxy.stop_signal` | signal sent to container for `stop` and `kill` methods | | docker's default | `SIGINT`, `SIGTERM`, `SIGHUP`, `SIGQUIT` and those without **SIG** prefix |
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
example: # matching `app.y.z`
|
||||
example: # matching `example.y.z`
|
||||
scheme: https
|
||||
host: 10.0.0.1
|
||||
port: 80
|
||||
path_patterns: # Check https://pkg.go.dev/net/http#hdr-Patterns-ServeMux for syntax
|
||||
- GET / # accept any GET request
|
||||
- POST /auth # for /auth and /auth/* accept only POST
|
||||
- GET /home/{$}
|
||||
- /b/{bucket}/o/{any}
|
||||
- GET /home/{$} # for exactly /home
|
||||
no_tls_verify: false
|
||||
set_headers:
|
||||
HEADER_A: VALUE_A, VALUE_B
|
||||
@@ -17,6 +16,6 @@ example: # matching `app.y.z`
|
||||
app1: # app1 -> localhost:8080
|
||||
port: 8080
|
||||
app2:
|
||||
scheme: tcp
|
||||
scheme: udp
|
||||
host: 10.0.0.2
|
||||
port: 20000:tcp
|
||||
port: 2223:dns
|
||||
|
||||
@@ -71,11 +71,9 @@ func (p *Provider) ObtainCert() (res E.NestedError) {
|
||||
}
|
||||
|
||||
if p.user.Registration == nil {
|
||||
if err := p.loadRegistration(); err.HasError() {
|
||||
if err := p.registerACME(); err.HasError() {
|
||||
b.Add(E.FailWith("register ACME", err))
|
||||
return
|
||||
}
|
||||
if err := p.registerACME(); err.HasError() {
|
||||
b.Add(E.FailWith("register ACME", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,16 +87,18 @@ func (p *Provider) ObtainCert() (res E.NestedError) {
|
||||
b.Add(err)
|
||||
return
|
||||
}
|
||||
err = p.saveCert(cert)
|
||||
if err.HasError() {
|
||||
|
||||
if err = p.saveCert(cert); err.HasError() {
|
||||
b.Add(E.FailWith("save certificate", err))
|
||||
return
|
||||
}
|
||||
|
||||
tlsCert, err := E.Check(tls.X509KeyPair(cert.Certificate, cert.PrivateKey))
|
||||
if err.HasError() {
|
||||
b.Add(E.FailWith("parse obtained certificate", err))
|
||||
return
|
||||
}
|
||||
|
||||
expiries, err := getCertExpiries(&tlsCert)
|
||||
if err.HasError() {
|
||||
b.Add(E.FailWith("get certificate expiry", err))
|
||||
@@ -187,29 +187,9 @@ func (p *Provider) registerACME() E.NestedError {
|
||||
}
|
||||
p.user.Registration = reg
|
||||
|
||||
if err := p.saveRegistration(); err.HasError() {
|
||||
logger.Warn(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) loadRegistration() E.NestedError {
|
||||
if p.user.Registration != nil {
|
||||
return nil
|
||||
}
|
||||
reg := ®istration.Resource{}
|
||||
err := U.LoadJson(RegistrationFile, reg)
|
||||
if err.HasError() {
|
||||
return E.FailWith("parse registration file", err)
|
||||
}
|
||||
p.user.Registration = reg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) saveRegistration() E.NestedError {
|
||||
return U.SaveJson(RegistrationFile, p.user.Registration, 0o600)
|
||||
}
|
||||
|
||||
func (p *Provider) saveCert(cert *certificate.Resource) E.NestedError {
|
||||
err := os.WriteFile(p.cfg.KeyPath, cert.PrivateKey, 0o600) // -rw-------
|
||||
if err != nil {
|
||||
|
||||
@@ -3,7 +3,7 @@ package autocert
|
||||
type CertState int
|
||||
|
||||
const (
|
||||
CertStateValid CertState = 0
|
||||
CertStateExpired CertState = iota
|
||||
CertStateMismatch CertState = iota
|
||||
CertStateValid CertState = iota
|
||||
CertStateExpired
|
||||
CertStateMismatch
|
||||
)
|
||||
|
||||
@@ -34,7 +34,7 @@ const DockerHostFromEnv = "$DOCKER_HOST"
|
||||
|
||||
const (
|
||||
IdleTimeoutDefault = "0"
|
||||
WakeTimeoutDefault = "10s"
|
||||
WakeTimeoutDefault = "30s"
|
||||
StopTimeoutDefault = "10s"
|
||||
StopMethodDefault = "stop"
|
||||
)
|
||||
|
||||
@@ -10,14 +10,15 @@ var (
|
||||
}
|
||||
|
||||
ServiceNamePortMapTCP = map[string]int{
|
||||
"mssql": 1433,
|
||||
"mysql": 3306,
|
||||
"mariadb": 3306,
|
||||
"postgres": 5432,
|
||||
"rabbitmq": 5672,
|
||||
"redis": 6379,
|
||||
"memcached": 11211,
|
||||
"mongo": 27017,
|
||||
"mssql": 1433,
|
||||
"mysql": 3306,
|
||||
"mariadb": 3306,
|
||||
"postgres": 5432,
|
||||
"rabbitmq": 5672,
|
||||
"redis": 6379,
|
||||
"memcached": 11211,
|
||||
"mongo": 27017,
|
||||
"minecraft-server": 25565,
|
||||
|
||||
"ssh": 22,
|
||||
"ftp": 21,
|
||||
@@ -53,7 +54,7 @@ var (
|
||||
"immich": 3001,
|
||||
"jellyfin": 8096,
|
||||
"lidarr": 8686,
|
||||
"minecraft-server": 25565,
|
||||
"microbin": 8080,
|
||||
"nginx": 80,
|
||||
"nginx-proxy-manager": 81,
|
||||
"open-webui": 8080,
|
||||
|
||||
@@ -9,31 +9,11 @@ import (
|
||||
U "github.com/yusing/go-proxy/utils"
|
||||
)
|
||||
|
||||
type ProxyProperties struct {
|
||||
DockerHost string `yaml:"-" json:"docker_host"`
|
||||
ContainerName string `yaml:"-" json:"container_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
|
||||
NetworkMode string `yaml:"-" json:"network_mode"`
|
||||
|
||||
Aliases []string `yaml:"-" json:"aliases"`
|
||||
IsExcluded bool `yaml:"-" json:"is_excluded"`
|
||||
IdleTimeout string `yaml:"-" json:"idle_timeout"`
|
||||
WakeTimeout string `yaml:"-" json:"wake_timeout"`
|
||||
StopMethod string `yaml:"-" json:"stop_method"`
|
||||
StopTimeout string `yaml:"-" json:"stop_timeout"` // stop_method = "stop" only
|
||||
StopSignal string `yaml:"-" json:"stop_signal"` // stop_method = "stop" | "kill" only
|
||||
Running bool `yaml:"-" json:"running"`
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
*types.Container
|
||||
*ProxyProperties
|
||||
}
|
||||
|
||||
type PortMapping = map[string]types.Port
|
||||
|
||||
func FromDocker(c *types.Container, dockerHost string) (res Container) {
|
||||
res.Container = c
|
||||
res.ProxyProperties = &ProxyProperties{
|
||||
@@ -119,9 +99,6 @@ func (c Container) getPublicPortMapping() PortMapping {
|
||||
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,7 +3,6 @@ package idlewatcher
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -18,14 +17,14 @@ func (rt roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
}
|
||||
|
||||
func (w *watcher) roundTrip(origRoundTrip roundTripFunc, req *http.Request) (*http.Response, error) {
|
||||
// wake the container
|
||||
w.wakeCh <- struct{}{}
|
||||
|
||||
// target site is ready, passthrough
|
||||
if w.ready.Load() {
|
||||
return origRoundTrip(req)
|
||||
}
|
||||
|
||||
// wake the container
|
||||
w.wakeCh <- struct{}{}
|
||||
|
||||
// initial request
|
||||
targetUrl := req.Header.Get(headerGoProxyTargetURL)
|
||||
if targetUrl == "" {
|
||||
@@ -57,7 +56,6 @@ func (w *watcher) roundTrip(origRoundTrip roundTripFunc, req *http.Request) (*ht
|
||||
rtDone <- resp
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -66,6 +64,10 @@ func (w *watcher) roundTrip(origRoundTrip roundTripFunc, req *http.Request) (*ht
|
||||
select {
|
||||
case resp := <-rtDone:
|
||||
return w.makeSuccResp(targetUrl, resp)
|
||||
case err := <-w.wakeDone:
|
||||
if err != nil {
|
||||
return w.makeErrResp("error waking up %s\n%s", w.ContainerName, err.Error())
|
||||
}
|
||||
case <-ctx.Done():
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return w.makeErrResp("Timed out waiting for %s to fully wake", w.ContainerName)
|
||||
|
||||
@@ -78,8 +78,8 @@ func Register(entry *P.ReverseProxyEntry) (*watcher, E.NestedError) {
|
||||
ReverseProxyEntry: entry,
|
||||
client: client,
|
||||
refCount: &sync.WaitGroup{},
|
||||
wakeCh: make(chan struct{}, 1),
|
||||
wakeDone: make(chan E.NestedError, 1),
|
||||
wakeCh: make(chan struct{}),
|
||||
wakeDone: make(chan E.NestedError),
|
||||
l: logger.WithField("container", entry.ContainerName),
|
||||
}
|
||||
w.refCount.Add(1)
|
||||
|
||||
22
src/docker/proxy_properties.go
Normal file
22
src/docker/proxy_properties.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package docker
|
||||
|
||||
import "github.com/docker/docker/api/types"
|
||||
|
||||
type PortMapping = map[string]types.Port
|
||||
type ProxyProperties struct {
|
||||
DockerHost string `yaml:"-" json:"docker_host"`
|
||||
ContainerName string `yaml:"-" json:"container_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
|
||||
NetworkMode string `yaml:"-" json:"network_mode"`
|
||||
|
||||
Aliases []string `yaml:"-" json:"aliases"`
|
||||
IsExcluded bool `yaml:"-" json:"is_excluded"`
|
||||
IdleTimeout string `yaml:"-" json:"idle_timeout"`
|
||||
WakeTimeout string `yaml:"-" json:"wake_timeout"`
|
||||
StopMethod string `yaml:"-" json:"stop_method"`
|
||||
StopTimeout string `yaml:"-" json:"stop_timeout"` // stop_method = "stop" only
|
||||
StopSignal string `yaml:"-" json:"stop_signal"` // stop_method = "stop" | "kill" only
|
||||
Running bool `yaml:"-" json:"running"`
|
||||
}
|
||||
@@ -133,13 +133,19 @@ func (ne NestedError) Subject(s any) NestedError {
|
||||
if ne == nil {
|
||||
return ne
|
||||
}
|
||||
var subject string
|
||||
switch ss := s.(type) {
|
||||
case string:
|
||||
ne.subject = ss
|
||||
subject = ss
|
||||
case fmt.Stringer:
|
||||
ne.subject = ss.String()
|
||||
subject = ss.String()
|
||||
default:
|
||||
ne.subject = fmt.Sprint(s)
|
||||
subject = fmt.Sprint(s)
|
||||
}
|
||||
if ne.subject == "" {
|
||||
ne.subject = subject
|
||||
} else {
|
||||
ne.subject = fmt.Sprintf("%s > %s", subject, ne.subject)
|
||||
}
|
||||
return ne
|
||||
}
|
||||
|
||||
@@ -34,62 +34,66 @@ var NewProxyEntries = F.NewMapOf[string, *RawEntry]
|
||||
|
||||
func (e *RawEntry) FillMissingFields() bool {
|
||||
isDocker := e.ProxyProperties != nil
|
||||
|
||||
if !isDocker {
|
||||
e.ProxyProperties = &D.ProxyProperties{}
|
||||
}
|
||||
|
||||
if e.Port == "" {
|
||||
if port, ok := ServiceNamePortMapTCP[e.ImageName]; ok {
|
||||
e.Port = strconv.Itoa(port)
|
||||
} else if port, ok := ImageNamePortMap[e.ImageName]; ok {
|
||||
e.Port = strconv.Itoa(port)
|
||||
lp, pp, extra := e.splitPorts()
|
||||
|
||||
if port, ok := ServiceNamePortMapTCP[e.ImageName]; ok {
|
||||
if pp == "" {
|
||||
pp = strconv.Itoa(port)
|
||||
}
|
||||
e.Scheme = "tcp"
|
||||
} else if port, ok := ImageNamePortMap[e.ImageName]; ok {
|
||||
if pp == "" {
|
||||
pp = strconv.Itoa(port)
|
||||
}
|
||||
e.Scheme = "http"
|
||||
} else if pp == "" && e.Scheme == "https" {
|
||||
pp = "443"
|
||||
} else if pp == "" {
|
||||
if p, ok := F.FirstValueOf(e.PrivatePortMapping); ok {
|
||||
pp = fmt.Sprint(p.PrivatePort)
|
||||
} else {
|
||||
switch {
|
||||
case e.Scheme == "https":
|
||||
e.Port = "443"
|
||||
case !isDocker:
|
||||
e.Port = "80"
|
||||
pp = "80"
|
||||
}
|
||||
}
|
||||
|
||||
// replace private port with public port (if any)
|
||||
if isDocker && e.NetworkMode != "host" {
|
||||
if p, ok := e.PrivatePortMapping[pp]; ok {
|
||||
pp = fmt.Sprint(p.PublicPort)
|
||||
}
|
||||
if _, ok := e.PublicPortMapping[pp]; !ok { // port is not exposed, but specified
|
||||
// try to fallback to first public port
|
||||
if p, ok := F.FirstValueOf(e.PublicPortMapping); ok {
|
||||
pp = fmt.Sprint(p.PublicPort)
|
||||
}
|
||||
// ignore only if it is NOT RUNNING
|
||||
// because stopped containers
|
||||
// will have empty port mapping got from docker
|
||||
if e.Running {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if e.PublicPortMapping != nil && e.NetworkMode != "host" {
|
||||
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
|
||||
}
|
||||
for _, p := range e.PublicPortMapping {
|
||||
e.Port = fmt.Sprint(p.PublicPort)
|
||||
break
|
||||
}
|
||||
if e.Scheme == "" && isDocker {
|
||||
if p, ok := e.PublicPortMapping[pp]; ok && p.Type == "udp" {
|
||||
e.Scheme = "udp"
|
||||
}
|
||||
}
|
||||
|
||||
if e.Scheme == "" {
|
||||
if _, ok := ServiceNamePortMapTCP[e.ImageName]; ok {
|
||||
if lp != "" {
|
||||
e.Scheme = "tcp"
|
||||
} else if strings.ContainsRune(e.Port, ':') {
|
||||
e.Scheme = "tcp"
|
||||
} else if _, ok := WellKnownHTTPPorts[e.Port]; ok {
|
||||
e.Scheme = "http"
|
||||
} else if e.Port == "443" {
|
||||
} else if strings.HasSuffix(pp, "443") {
|
||||
e.Scheme = "https"
|
||||
} else if isDocker {
|
||||
if e.Port == "" {
|
||||
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 if _, ok := WellKnownHTTPPorts[pp]; ok {
|
||||
e.Scheme = "http"
|
||||
} else {
|
||||
// assume its http
|
||||
e.Scheme = "http"
|
||||
}
|
||||
}
|
||||
@@ -97,7 +101,6 @@ func (e *RawEntry) FillMissingFields() bool {
|
||||
if e.Host == "" {
|
||||
e.Host = "localhost"
|
||||
}
|
||||
|
||||
if e.IdleTimeout == "" {
|
||||
e.IdleTimeout = IdleTimeoutDefault
|
||||
}
|
||||
@@ -111,5 +114,35 @@ func (e *RawEntry) FillMissingFields() bool {
|
||||
e.StopMethod = StopMethodDefault
|
||||
}
|
||||
|
||||
e.Port = joinPorts(lp, pp, extra)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *RawEntry) splitPorts() (lp string, pp string, extra string) {
|
||||
portSplit := strings.Split(e.Port, ":")
|
||||
if len(portSplit) == 1 {
|
||||
pp = portSplit[0]
|
||||
} else {
|
||||
lp = portSplit[0]
|
||||
pp = portSplit[1]
|
||||
}
|
||||
if len(portSplit) > 2 {
|
||||
extra = strings.Join(portSplit[2:], ":")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func joinPorts(lp string, pp string, extra string) string {
|
||||
s := make([]string, 0, 3)
|
||||
if lp != "" {
|
||||
s = append(s, lp)
|
||||
}
|
||||
if pp != "" {
|
||||
s = append(s, pp)
|
||||
}
|
||||
if extra != "" {
|
||||
s = append(s, extra)
|
||||
}
|
||||
return strings.Join(s, ":")
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func ValidateEntry(m *M.RawEntry) (any, E.NestedError) {
|
||||
}
|
||||
|
||||
var entry any
|
||||
e := E.NewBuilder("error validating proxy entry")
|
||||
e := E.NewBuilder("error validating entry")
|
||||
if scheme.IsStream() {
|
||||
entry = validateStreamEntry(m, e)
|
||||
} else {
|
||||
|
||||
@@ -7,6 +7,6 @@ import (
|
||||
type Host string
|
||||
type Subdomain = Alias
|
||||
|
||||
func ValidateHost(s string) (Host, E.NestedError) {
|
||||
func ValidateHost[String ~string](s String) (Host, E.NestedError) {
|
||||
return Host(s), nil
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
type Port int
|
||||
|
||||
func ValidatePort(v string) (Port, E.NestedError) {
|
||||
p, err := strconv.Atoi(v)
|
||||
func ValidatePort[String ~string](v String) (Port, E.NestedError) {
|
||||
p, err := strconv.Atoi(string(v))
|
||||
if err != nil {
|
||||
return ErrPort, E.Invalid("port number", v).With(err)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
type Scheme string
|
||||
|
||||
func NewScheme(s string) (Scheme, E.NestedError) {
|
||||
func NewScheme[String ~string](s String) (Scheme, E.NestedError) {
|
||||
switch s {
|
||||
case "http", "https", "tcp", "udp":
|
||||
return Scheme(s), nil
|
||||
|
||||
@@ -26,18 +26,19 @@ func ValidateStreamPort(p string) (StreamPort, E.NestedError) {
|
||||
|
||||
listeningPort, err := ValidatePort(split[0])
|
||||
if err != nil {
|
||||
return ErrStreamPort, err
|
||||
return ErrStreamPort, err.Subject("listening port")
|
||||
}
|
||||
|
||||
proxyPort, err := ValidatePort(split[1])
|
||||
|
||||
if err.Is(E.ErrOutOfRange) {
|
||||
return ErrStreamPort, err
|
||||
return ErrStreamPort, err.Subject("proxy port")
|
||||
} else if proxyPort == 0 {
|
||||
return ErrStreamPort, E.Invalid("stream port", p).With("proxy port cannot be 0")
|
||||
return ErrStreamPort, E.Invalid("proxy port", p)
|
||||
} else if err != nil {
|
||||
proxyPort, err = parseNameToPort(split[1])
|
||||
if err != nil {
|
||||
return ErrStreamPort, E.Invalid("stream port", p).With(proxyPort)
|
||||
return ErrStreamPort, E.Invalid("proxy port", proxyPort)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
D "github.com/yusing/go-proxy/docker"
|
||||
E "github.com/yusing/go-proxy/error"
|
||||
@@ -123,6 +122,10 @@ func (p *DockerProvider) OnEvent(event W.Event, routes R.Routes) (res EventResul
|
||||
func (p *DockerProvider) entriesFromContainerLabels(container D.Container) (M.RawEntries, E.NestedError) {
|
||||
entries := M.NewProxyEntries()
|
||||
|
||||
if container.IsExcluded {
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// init entries map for all aliases
|
||||
for _, a := range container.Aliases {
|
||||
entries.Store(a, &M.RawEntry{
|
||||
@@ -137,30 +140,11 @@ func (p *DockerProvider) entriesFromContainerLabels(container D.Container) (M.Ra
|
||||
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
|
||||
entries.RemoveAll(func(re *M.RawEntry) bool {
|
||||
return !re.FillMissingFields()
|
||||
})
|
||||
|
||||
// do it again since the port may got filled in
|
||||
replacePrivPorts()
|
||||
|
||||
return entries, errors.Build().Subject(container.ContainerName)
|
||||
}
|
||||
|
||||
|
||||
@@ -249,7 +249,9 @@ func TestImplicitExclude(t *testing.T) {
|
||||
Labels: map[string]string{
|
||||
D.LabelAliases: "a",
|
||||
"proxy.a.no_tls_verify": "true",
|
||||
}}, ""))
|
||||
},
|
||||
State: "running",
|
||||
}, ""))
|
||||
ExpectNoError(t, err.Error())
|
||||
|
||||
_, ok := entries.Load("a")
|
||||
@@ -264,6 +266,7 @@ func TestImplicitExcludeNoExposedPort(t *testing.T) {
|
||||
Ports: []types.Port{
|
||||
{Type: "tcp", PrivatePort: 6379, PublicPort: 0}, // not exposed
|
||||
},
|
||||
State: "running",
|
||||
}, ""))
|
||||
ExpectNoError(t, err.Error())
|
||||
|
||||
@@ -271,7 +274,7 @@ func TestImplicitExcludeNoExposedPort(t *testing.T) {
|
||||
ExpectFalse(t, ok)
|
||||
}
|
||||
|
||||
func TestExcludeNonExposedPort(t *testing.T) {
|
||||
func TestNotExcludeSpecifiedPort(t *testing.T) {
|
||||
var p DockerProvider
|
||||
entries, err := p.entriesFromContainerLabels(D.FromDocker(&types.Container{
|
||||
Image: "redis",
|
||||
@@ -280,13 +283,13 @@ func TestExcludeNonExposedPort(t *testing.T) {
|
||||
{Type: "tcp", PrivatePort: 6379, PublicPort: 0}, // not exposed
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"proxy.redis.port": "6379:6379", // should be excluded even specified
|
||||
"proxy.redis.port": "6379:6379", // but specified in label
|
||||
},
|
||||
}, ""))
|
||||
ExpectNoError(t, err.Error())
|
||||
|
||||
_, ok := entries.Load("redis")
|
||||
ExpectFalse(t, ok)
|
||||
ExpectTrue(t, ok)
|
||||
}
|
||||
|
||||
func TestNotExcludeNonExposedPortHostNetwork(t *testing.T) {
|
||||
@@ -298,7 +301,7 @@ func TestNotExcludeNonExposedPortHostNetwork(t *testing.T) {
|
||||
{Type: "tcp", PrivatePort: 6379, PublicPort: 0}, // not exposed
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"proxy.redis.port": "6379:6379", // should be excluded even specified
|
||||
"proxy.redis.port": "6379:6379",
|
||||
},
|
||||
}
|
||||
cont.HostConfig.NetworkMode = "host"
|
||||
|
||||
@@ -148,7 +148,12 @@ func ProxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func findMux(host string) (*http.ServeMux, E.NestedError) {
|
||||
sd := strings.Split(host, ".")[0]
|
||||
hostSplit := strings.Split(host, ".")
|
||||
n := len(hostSplit)
|
||||
if n <= 2 {
|
||||
return nil, E.Missing("subdomain")
|
||||
}
|
||||
sd := strings.Join(hostSplit[:n-2], ".")
|
||||
if r, ok := httpRoutes.Load(PT.Alias(sd)); ok {
|
||||
return r.mux, nil
|
||||
}
|
||||
|
||||
8
src/utils/functional/map_utils.go
Normal file
8
src/utils/functional/map_utils.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package functional
|
||||
|
||||
func FirstValueOf[KT comparable, VT any](m map[KT]VT) (_ VT, ok bool) {
|
||||
for _, v := range m {
|
||||
return v, true
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -100,7 +100,6 @@ func Serialize(data any) (SerializedObject, E.NestedError) {
|
||||
}
|
||||
}
|
||||
default:
|
||||
// return nil, fmt.Errorf("unsupported type: %s", value.Kind())
|
||||
return nil, E.Unsupported("type", value.Kind())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user