mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-14 04:29:39 +02:00
Compare commits
3 Commits
0.5.0-beta
...
0.5.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
719693deb7 | ||
|
|
23e7d06081 | ||
|
|
85fb637551 |
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.22.6-alpine as builder
|
FROM golang:1.22.6-alpine AS builder
|
||||||
COPY src /src
|
COPY src /src
|
||||||
ENV GOCACHE=/root/.cache/go-build
|
ENV GOCACHE=/root/.cache/go-build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -12,7 +12,7 @@ build:
|
|||||||
CGO_ENABLED=0 GOOS=linux go build -pgo=auto -o bin/go-proxy github.com/yusing/go-proxy
|
CGO_ENABLED=0 GOOS=linux go build -pgo=auto -o bin/go-proxy github.com/yusing/go-proxy
|
||||||
|
|
||||||
test:
|
test:
|
||||||
cd src && go test && cd ..
|
cd src && go test ./... && cd ..
|
||||||
|
|
||||||
up:
|
up:
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -16,7 +16,6 @@ A [lightweight](docs/benchmark_result.md), easy-to-use, and efficient reverse pr
|
|||||||
- [Provider File](#provider-file)
|
- [Provider File](#provider-file)
|
||||||
- [Known issues](#known-issues)
|
- [Known issues](#known-issues)
|
||||||
- [Build it yourself](#build-it-yourself)
|
- [Build it yourself](#build-it-yourself)
|
||||||
<!-- /TOC -->
|
|
||||||
|
|
||||||
## Key Points
|
## Key Points
|
||||||
|
|
||||||
@@ -81,12 +80,14 @@ autocert:
|
|||||||
- ...
|
- ...
|
||||||
# reverse proxy providers configuration
|
# reverse proxy providers configuration
|
||||||
providers:
|
providers:
|
||||||
entry_1:
|
include:
|
||||||
kind: docker
|
- providers.yml
|
||||||
value: # `FROM_ENV` or full url to docker host
|
- other_file_1.yml
|
||||||
entry_2:
|
- ...
|
||||||
kind: file
|
docker:
|
||||||
value: # relative path of file to `config/`
|
local: $DOCKER_HOST
|
||||||
|
remote-1: tcp://10.0.2.1:2375
|
||||||
|
remote-2: ssh://root:1234@10.0.2.2
|
||||||
```
|
```
|
||||||
|
|
||||||
[🔼Back to top](#table-of-content)
|
[🔼Back to top](#table-of-content)
|
||||||
|
|||||||
@@ -11,22 +11,26 @@
|
|||||||
# provider: cloudflare
|
# provider: cloudflare
|
||||||
# email: # ACME Email
|
# email: # ACME Email
|
||||||
# domains: # a list of domains for cert registration
|
# domains: # a list of domains for cert registration
|
||||||
# -
|
# - x.y.z
|
||||||
# options:
|
# options:
|
||||||
# - auth_token: # your zone API token
|
# - auth_token: c1234565789-abcdefghijklmnopqrst # your zone API token
|
||||||
|
|
||||||
# 3. other providers, check readme for more
|
# 3. other providers, check readme for more
|
||||||
|
|
||||||
providers:
|
providers:
|
||||||
local:
|
include:
|
||||||
kind: docker
|
- providers.yml # config/providers.yml
|
||||||
|
# add some more below if you want
|
||||||
|
# - file1.yml # config/file_1.yml
|
||||||
|
# - file2.yml
|
||||||
|
docker:
|
||||||
# for value format, see https://docs.docker.com/reference/cli/dockerd/
|
# for value format, see https://docs.docker.com/reference/cli/dockerd/
|
||||||
# i.e. ssh://user@10.0.1.1:22, tcp://10.0.2.1:2375
|
# $DOCKER_HOST implies unix:///var/run/docker.sock by default
|
||||||
# use FROM_ENV if you have binded docker socket to /var/run/docker.sock
|
local: $DOCKER_HOST
|
||||||
value: FROM_ENV
|
# add more docker providers if needed
|
||||||
providers:
|
# remote-1: tcp://10.0.2.1:2375
|
||||||
kind: file
|
# remote-2: ssh://root:1234@10.0.2.2
|
||||||
value: providers.yml
|
|
||||||
# Fixed options (optional, non hot-reloadable)
|
# Fixed options (optional, non hot-reloadable)
|
||||||
|
|
||||||
# timeout_shutdown: 5
|
# timeout_shutdown: 5
|
||||||
|
|||||||
@@ -200,25 +200,22 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- nginx:/usr/share/nginx/html
|
- nginx:/usr/share/nginx/html
|
||||||
go-proxy:
|
go-proxy:
|
||||||
image: ghcr.io/yusing/go-proxy
|
image: ghcr.io/yusing/go-proxy:latest
|
||||||
container_name: go-proxy
|
container_name: go-proxy
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
network_mode: host
|
||||||
- 80:80 # http
|
|
||||||
- 443:443 # optional, https
|
|
||||||
- 8080:8080 # http panel
|
|
||||||
- 8443:8443 # optional, https panel
|
|
||||||
|
|
||||||
- 53:20000/udp # adguardhome
|
|
||||||
- 25565:20001/tcp # minecraft
|
|
||||||
- 8211:20002/udp # palworld
|
|
||||||
- 27015:20003/udp # palworld
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./config:/app/config
|
- ./config:/app/config
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
go-proxy-frontend:
|
||||||
|
image: ghcr.io/yusing/go-proxy-frontend:latest
|
||||||
|
container_name: go-proxy-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: host
|
||||||
labels:
|
labels:
|
||||||
- proxy.aliases=gp
|
- proxy.*.aliases=gp
|
||||||
- proxy.gp.port=8080
|
depends_on:
|
||||||
|
- go-proxy
|
||||||
```
|
```
|
||||||
|
|
||||||
[🔼Back to top](#table-of-content)
|
[🔼Back to top](#table-of-content)
|
||||||
|
|||||||
@@ -34,12 +34,7 @@
|
|||||||
"provider": {
|
"provider": {
|
||||||
"title": "DNS Challenge Provider",
|
"title": "DNS Challenge Provider",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["local", "cloudflare", "clouddns", "duckdns"]
|
||||||
"local",
|
|
||||||
"cloudflare",
|
|
||||||
"clouddns",
|
|
||||||
"duckdns"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"title": "Provider specific options",
|
"title": "Provider specific options",
|
||||||
@@ -57,12 +52,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"then": {
|
"then": {
|
||||||
"required": [
|
"required": ["email", "domains", "provider", "options"]
|
||||||
"email",
|
|
||||||
"domains",
|
|
||||||
"provider",
|
|
||||||
"options"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -76,9 +66,7 @@
|
|||||||
"then": {
|
"then": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"options": {
|
"options": {
|
||||||
"required": [
|
"required": ["auth_token"],
|
||||||
"auth_token"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"auth_token": {
|
"auth_token": {
|
||||||
@@ -101,11 +89,7 @@
|
|||||||
"then": {
|
"then": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"options": {
|
"options": {
|
||||||
"required": [
|
"required": ["client_id", "email", "password"],
|
||||||
"client_id",
|
|
||||||
"email",
|
|
||||||
"password"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"client_id": {
|
"client_id": {
|
||||||
@@ -136,9 +120,7 @@
|
|||||||
"then": {
|
"then": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"options": {
|
"options": {
|
||||||
"required": [
|
"required": ["token"],
|
||||||
"token"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"token": {
|
"token": {
|
||||||
@@ -155,73 +137,54 @@
|
|||||||
"providers": {
|
"providers": {
|
||||||
"title": "Proxy providers configuration",
|
"title": "Proxy providers configuration",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"patternProperties": {
|
"additionalProperties": false,
|
||||||
"^[a-zA-Z0-9_-]+$": {
|
"properties": {
|
||||||
"description": "Proxy provider",
|
"include": {
|
||||||
|
"title": "Proxy providers configuration files",
|
||||||
|
"description": "relative path to 'config'",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[a-zA-Z0-9_-]+\\.(yml|yaml)$",
|
||||||
|
"patternErrorMessage": "Invalid file name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"docker": {
|
||||||
|
"title": "Docker provider configuration",
|
||||||
|
"description": "docker clients (name: address)",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"patternProperties": {
|
||||||
"kind": {
|
"^[a-zA-Z0-9-_]+$": {
|
||||||
"description": "Proxy provider kind",
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"examples": [
|
||||||
"docker",
|
"unix:///var/run/docker.sock",
|
||||||
"file"
|
"tcp://127.0.0.1:2375",
|
||||||
|
"ssh://user@host:port"
|
||||||
|
],
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"const": "$DOCKER_HOST",
|
||||||
|
"description": "Use DOCKER_HOST environment variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^unix://.+$",
|
||||||
|
"description": "A Unix socket for local Docker communication."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^ssh://.+$",
|
||||||
|
"description": "An SSH connection to a remote Docker host."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^fd://.+$",
|
||||||
|
"description": "A file descriptor for Docker communication."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^tcp://.+$",
|
||||||
|
"description": "A TCP connection to a remote Docker host."
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"required": [
|
|
||||||
"kind",
|
|
||||||
"value"
|
|
||||||
],
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"if": {
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"const": "docker"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"then": {
|
|
||||||
"if": {
|
|
||||||
"properties": {
|
|
||||||
"value": {
|
|
||||||
"const": "FROM_ENV"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"then": {
|
|
||||||
"properties": {
|
|
||||||
"value": {
|
|
||||||
"description": "use docker client from environment"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"else": {
|
|
||||||
"properties": {
|
|
||||||
"value": {
|
|
||||||
"description": "docker client URL",
|
|
||||||
"examples": [
|
|
||||||
"unix:///var/run/docker.sock",
|
|
||||||
"tcp://127.0.0.1:2375",
|
|
||||||
"ssh://user@host:port"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"else": {
|
|
||||||
"properties": {
|
|
||||||
"value": {
|
|
||||||
"description": "file path"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -236,7 +199,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": ["providers"]
|
||||||
"providers"
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"host": {
|
"host": {
|
||||||
"anyOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "ipv4",
|
"format": "ipv4",
|
||||||
@@ -69,9 +69,7 @@
|
|||||||
"set_headers": {},
|
"set_headers": {},
|
||||||
"hide_headers": {}
|
"hide_headers": {}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["host"],
|
||||||
"host"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
@@ -80,10 +78,7 @@
|
|||||||
{
|
{
|
||||||
"properties": {
|
"properties": {
|
||||||
"scheme": {
|
"scheme": {
|
||||||
"enum": [
|
"enum": ["http", "https"]
|
||||||
"http",
|
|
||||||
"https"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -171,9 +166,7 @@
|
|||||||
"not": true
|
"not": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["port"]
|
||||||
"port"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -198,4 +191,4 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
|
|||||||
114
setup-binary.sh
114
setup-binary.sh
@@ -1,114 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
REPO_URL=https://github.com/yusing/go-proxy
|
|
||||||
BIN_URL="${REPO_URL}/releases/download/${VERSION}/go-proxy"
|
|
||||||
SRC_URL="${REPO_URL}/archive/refs/tags/${VERSION}.tar.gz"
|
|
||||||
APP_ROOT="/opt/go-proxy/${VERSION}"
|
|
||||||
LOG_FILE="/tmp/go-proxy-setup.log"
|
|
||||||
|
|
||||||
if [ -z "$VERSION" ] || [ "$VERSION" = "latest" ]; then
|
|
||||||
VERSION_URL="${REPO_URL}/raw/main/version.txt"
|
|
||||||
VERSION=$(wget -qO- "$VERSION_URL")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "$APP_ROOT" ]; then
|
|
||||||
echo "$APP_ROOT already exists"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# check if wget exists
|
|
||||||
if ! [ -x "$(command -v wget)" ]; then
|
|
||||||
echo "wget is not installed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# check if make exists
|
|
||||||
if ! [ -x "$(command -v make)" ]; then
|
|
||||||
echo "make is not installed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
dl_source() {
|
|
||||||
cd /tmp
|
|
||||||
echo "Downloading go-proxy source ${VERSION}"
|
|
||||||
wget -c "${SRC_URL}" -O go-proxy.tar.gz &> $LOG_FILE
|
|
||||||
if [ $? -gt 0 ]; then
|
|
||||||
echo "Source download failed, check your internet connection and version number"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Done"
|
|
||||||
echo "Extracting go-proxy source ${VERSION}"
|
|
||||||
tar xzf go-proxy.tar.gz &> $LOG_FILE
|
|
||||||
if [ $? -gt 0 ]; then
|
|
||||||
echo "failed to untar go-proxy.tar.gz"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
rm go-proxy.tar.gz
|
|
||||||
mkdir -p "$(dirname "${APP_ROOT}")"
|
|
||||||
mv "go-proxy-${VERSION}" "$APP_ROOT"
|
|
||||||
cd "$APP_ROOT"
|
|
||||||
echo "Done"
|
|
||||||
}
|
|
||||||
dl_binary() {
|
|
||||||
mkdir -p bin
|
|
||||||
echo "Downloading go-proxy binary ${VERSION}"
|
|
||||||
wget -c "${BIN_URL}" -O bin/go-proxy &> $LOG_FILE
|
|
||||||
if [ $? -gt 0 ]; then
|
|
||||||
echo "Binary download failed, check your internet connection and version number"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
chmod +x bin/go-proxy
|
|
||||||
echo "Done"
|
|
||||||
}
|
|
||||||
setup() {
|
|
||||||
make setup &> $LOG_FILE
|
|
||||||
if [ $? -gt 0 ]; then
|
|
||||||
echo "make setup failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# SETUP_CODEMIRROR = 1
|
|
||||||
if [ "$SETUP_CODEMIRROR" != "0" ]; then
|
|
||||||
make setup-codemirror &> $LOG_FILE || echo "make setup-codemirror failed, ignored"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
dl_source
|
|
||||||
dl_binary
|
|
||||||
setup
|
|
||||||
|
|
||||||
# setup systemd
|
|
||||||
|
|
||||||
# check if systemctl exists
|
|
||||||
if ! command -v systemctl is-system-running > /dev/null 2>&1; then
|
|
||||||
echo "systemctl not found, skipping systemd setup"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
systemctl_failed() {
|
|
||||||
echo "Failed to enable and start go-proxy"
|
|
||||||
systemctl status go-proxy
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
echo "Setting up systemd service"
|
|
||||||
cat <<EOF > /etc/systemd/system/go-proxy.service
|
|
||||||
[Unit]
|
|
||||||
Description=go-proxy reverse proxy
|
|
||||||
After=network-online.target
|
|
||||||
Wants=network-online.target systemd-networkd-wait-online.service
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=${APP_ROOT}/bin/go-proxy
|
|
||||||
WorkingDirectory=${APP_ROOT}
|
|
||||||
Environment="GOPROXY_IS_SYSTEMD=1"
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=1s
|
|
||||||
KillMode=process
|
|
||||||
KillSignal=SIGINT
|
|
||||||
TimeoutStartSec=5s
|
|
||||||
TimeoutStopSec=5s
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
systemctl daemon-reload &>$LOG_FILE || systemctl_failed
|
|
||||||
systemctl enable --now go-proxy &>$LOG_FILE || systemctl_failed
|
|
||||||
echo "Done"
|
|
||||||
echo "Setup complete"
|
|
||||||
@@ -21,6 +21,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var providersGenMap = map[string]ProviderGenerator{
|
var providersGenMap = map[string]ProviderGenerator{
|
||||||
|
"": providerGenerator(NewDummyDefaultConfig, NewDummyDNSProviderConfig),
|
||||||
ProviderLocal: providerGenerator(NewDummyDefaultConfig, NewDummyDNSProviderConfig),
|
ProviderLocal: providerGenerator(NewDummyDefaultConfig, NewDummyDNSProviderConfig),
|
||||||
ProviderCloudflare: providerGenerator(cloudflare.NewDefaultConfig, cloudflare.NewDNSProviderConfig),
|
ProviderCloudflare: providerGenerator(cloudflare.NewDefaultConfig, cloudflare.NewDNSProviderConfig),
|
||||||
ProviderClouddns: providerGenerator(clouddns.NewDefaultConfig, clouddns.NewDNSProviderConfig),
|
ProviderClouddns: providerGenerator(clouddns.NewDefaultConfig, clouddns.NewDNSProviderConfig),
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const (
|
|||||||
ProvidersSchemaPath = SchemaBasePath + "providers.schema.json"
|
ProvidersSchemaPath = SchemaBasePath + "providers.schema.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DockerHostFromEnv = "FROM_ENV"
|
const DockerHostFromEnv = "$DOCKER_HOST"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ProxyHTTPPort = ":80"
|
ProxyHTTPPort = ":80"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var IsRunningAsService = getEnvBool("GOPROXY_IS_SYSTEMD")
|
|
||||||
var NoSchemaValidation = getEnvBool("GOPROXY_NO_SCHEMA_VALIDATION")
|
var NoSchemaValidation = getEnvBool("GOPROXY_NO_SCHEMA_VALIDATION")
|
||||||
var IsDebug = getEnvBool("GOPROXY_DEBUG")
|
var IsDebug = getEnvBool("GOPROXY_DEBUG")
|
||||||
|
|
||||||
|
|||||||
@@ -134,16 +134,9 @@ func (cfg *Config) Statistics() map[string]interface{} {
|
|||||||
panic("bug: should not reach here")
|
panic("bug: should not reach here")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
stats["type"] = p.GetType()
|
||||||
stats["num_streams"] = nStreams
|
stats["num_streams"] = nStreams
|
||||||
stats["num_reverse_proxies"] = nRPs
|
stats["num_reverse_proxies"] = nRPs
|
||||||
switch p.ProviderImpl.(type) {
|
|
||||||
case *PR.DockerProvider:
|
|
||||||
stats["type"] = "docker"
|
|
||||||
case *PR.FileProvider:
|
|
||||||
stats["type"] = "file"
|
|
||||||
default:
|
|
||||||
panic("bug: should not reach here")
|
|
||||||
}
|
|
||||||
providerStats[p.GetName()] = stats
|
providerStats[p.GetName()] = stats
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -202,17 +195,17 @@ func (cfg *Config) load() E.NestedError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !common.NoSchemaValidation {
|
if !common.NoSchemaValidation {
|
||||||
if err := Validate(data); err.IsNotNil() {
|
if err = Validate(data); err.IsNotNil() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errors := E.NewBuilder("errors validating config")
|
warnings := E.NewBuilder("errors validating config")
|
||||||
|
|
||||||
cfg.l.Debug("starting autocert")
|
cfg.l.Debug("starting autocert")
|
||||||
ap, err := autocert.NewConfig(&model.AutoCert).GetProvider()
|
ap, err := autocert.NewConfig(&model.AutoCert).GetProvider()
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
errors.Add(E.Failure("autocert provider").With(err))
|
warnings.Add(E.Failure("autocert provider").With(err))
|
||||||
} else {
|
} else {
|
||||||
cfg.l.Debug("started autocert")
|
cfg.l.Debug("started autocert")
|
||||||
}
|
}
|
||||||
@@ -220,18 +213,24 @@ func (cfg *Config) load() E.NestedError {
|
|||||||
|
|
||||||
cfg.l.Debug("starting providers")
|
cfg.l.Debug("starting providers")
|
||||||
cfg.proxyProviders = F.NewMap[string, *PR.Provider]()
|
cfg.proxyProviders = F.NewMap[string, *PR.Provider]()
|
||||||
for name, pm := range model.Providers {
|
for _, filename := range model.Providers.Files {
|
||||||
p := PR.NewProvider(name, pm)
|
p := PR.NewFileProvider(filename)
|
||||||
cfg.proxyProviders.Set(name, p)
|
cfg.proxyProviders.Set(p.GetName(), p)
|
||||||
if err := p.StartAllRoutes(); err.IsNotNil() {
|
|
||||||
errors.Add(E.Failure("start routes").Subjectf("provider %s", name).With(err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
for name, dockerHost := range model.Providers.Docker {
|
||||||
|
p := PR.NewDockerProvider(name, dockerHost)
|
||||||
|
cfg.proxyProviders.Set(p.GetName(), p)
|
||||||
|
}
|
||||||
|
cfg.proxyProviders.EachKV(func(name string, p *PR.Provider) {
|
||||||
|
if err := p.StartAllRoutes(); err.IsNotNil() {
|
||||||
|
warnings.Add(E.Failure("start routes").Subject(p).With(err))
|
||||||
|
}
|
||||||
|
})
|
||||||
cfg.l.Debug("started providers")
|
cfg.l.Debug("started providers")
|
||||||
|
|
||||||
cfg.value = model
|
cfg.value = model
|
||||||
|
|
||||||
if err := errors.Build(); err.IsNotNil() {
|
if err := warnings.Build(); err.IsNotNil() {
|
||||||
cfg.l.Warn(err)
|
cfg.l.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +243,7 @@ func (cfg *Config) controlProviders(action string, do func(*PR.Provider) E.Neste
|
|||||||
|
|
||||||
cfg.proxyProviders.EachKVParallel(func(name string, p *PR.Provider) {
|
cfg.proxyProviders.EachKVParallel(func(name string, p *PR.Provider) {
|
||||||
if err := do(p); err.IsNotNil() {
|
if err := do(p); err.IsNotNil() {
|
||||||
errors.Add(E.From(err).Subjectf("provider %s", name))
|
errors.Add(E.From(err).Subject(p))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
@@ -77,5 +76,3 @@ func RegisterNamespace(namespace string, pm ValueParserMap) {
|
|||||||
|
|
||||||
// namespace:target.attribute -> func(string) (any, error)
|
// namespace:target.attribute -> func(string) (any, error)
|
||||||
var labelValueParserMap = make(map[string]ValueParserMap)
|
var labelValueParserMap = make(map[string]ValueParserMap)
|
||||||
|
|
||||||
var ErrInvalidLabel = errors.New("invalid label")
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -14,27 +13,13 @@ func makeLabel(namespace string, alias string, field string) string {
|
|||||||
return fmt.Sprintf("%s.%s.%s", namespace, alias, field)
|
return fmt.Sprintf("%s.%s.%s", namespace, alias, field)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidLabel(t *testing.T) {
|
|
||||||
pl, err := ParseLabel("foo.bar", "1234")
|
|
||||||
if !errors.Is(err, ErrInvalidLabel) {
|
|
||||||
t.Errorf("expected errInvalidLabel, got %s", err)
|
|
||||||
}
|
|
||||||
if pl != nil {
|
|
||||||
t.Errorf("expected nil, got %v", pl)
|
|
||||||
}
|
|
||||||
_, err = ParseLabel("proxy.foo", "bar")
|
|
||||||
if !errors.Is(err, ErrInvalidLabel) {
|
|
||||||
t.Errorf("expected errInvalidLabel, got %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHomePageLabel(t *testing.T) {
|
func TestHomePageLabel(t *testing.T) {
|
||||||
alias := "foo"
|
alias := "foo"
|
||||||
field := "ip"
|
field := "ip"
|
||||||
v := "bar"
|
v := "bar"
|
||||||
pl, err := ParseLabel(makeLabel(NSHomePage, alias, field), v)
|
pl, err := ParseLabel(makeLabel(NSHomePage, alias, field), v)
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
t.Errorf("expected err=nil, got %s", err)
|
t.Errorf("expected err=nil, got %s", err.Error())
|
||||||
}
|
}
|
||||||
if pl.Target != alias {
|
if pl.Target != alias {
|
||||||
t.Errorf("expected alias=%s, got %s", alias, pl.Target)
|
t.Errorf("expected alias=%s, got %s", alias, pl.Target)
|
||||||
@@ -53,7 +38,7 @@ func TestStringProxyLabel(t *testing.T) {
|
|||||||
v := "bar"
|
v := "bar"
|
||||||
pl, err := ParseLabel(makeLabel(NSProxy, alias, field), v)
|
pl, err := ParseLabel(makeLabel(NSProxy, alias, field), v)
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
t.Errorf("expected err=nil, got %s", err)
|
t.Errorf("expected err=nil, got %s", err.Error())
|
||||||
}
|
}
|
||||||
if pl.Target != alias {
|
if pl.Target != alias {
|
||||||
t.Errorf("expected alias=%s, got %s", alias, pl.Target)
|
t.Errorf("expected alias=%s, got %s", alias, pl.Target)
|
||||||
@@ -83,7 +68,7 @@ func TestBoolProxyLabelValid(t *testing.T) {
|
|||||||
for k, v := range tests {
|
for k, v := range tests {
|
||||||
pl, err := ParseLabel(makeLabel(NSProxy, alias, field), k)
|
pl, err := ParseLabel(makeLabel(NSProxy, alias, field), k)
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
t.Errorf("expected err=nil, got %s", err)
|
t.Errorf("expected err=nil, got %s", err.Error())
|
||||||
}
|
}
|
||||||
if pl.Target != alias {
|
if pl.Target != alias {
|
||||||
t.Errorf("expected alias=%s, got %s", alias, pl.Target)
|
t.Errorf("expected alias=%s, got %s", alias, pl.Target)
|
||||||
@@ -101,8 +86,8 @@ func TestBoolProxyLabelInvalid(t *testing.T) {
|
|||||||
alias := "foo"
|
alias := "foo"
|
||||||
field := "no_tls_verify"
|
field := "no_tls_verify"
|
||||||
_, err := ParseLabel(makeLabel(NSProxy, alias, field), "invalid")
|
_, err := ParseLabel(makeLabel(NSProxy, alias, field), "invalid")
|
||||||
if !errors.Is(err, E.ErrInvalid) {
|
if !err.Is(E.ErrInvalid) {
|
||||||
t.Errorf("expected err InvalidProxyLabel, got %s", err)
|
t.Errorf("expected err InvalidProxyLabel, got %v", reflect.TypeOf(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +106,7 @@ func TestHeaderProxyLabelValid(t *testing.T) {
|
|||||||
|
|
||||||
pl, err := ParseLabel(makeLabel(NSProxy, alias, field), v)
|
pl, err := ParseLabel(makeLabel(NSProxy, alias, field), v)
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
t.Errorf("expected err=nil, got %s", err)
|
t.Errorf("expected err=nil, got %s", err.Error())
|
||||||
}
|
}
|
||||||
if pl.Target != alias {
|
if pl.Target != alias {
|
||||||
t.Errorf("expected alias=%s, got %s", alias, pl.Target)
|
t.Errorf("expected alias=%s, got %s", alias, pl.Target)
|
||||||
@@ -152,7 +137,7 @@ func TestHeaderProxyLabelInvalid(t *testing.T) {
|
|||||||
|
|
||||||
for _, v := range tests {
|
for _, v := range tests {
|
||||||
_, err := ParseLabel(makeLabel(NSProxy, alias, field), v)
|
_, err := ParseLabel(makeLabel(NSProxy, alias, field), v)
|
||||||
if !errors.Is(err, E.ErrInvalid) {
|
if !err.Is(E.ErrInvalid) {
|
||||||
t.Errorf("expected err InvalidProxyLabel for %q, got %v", v, err)
|
t.Errorf("expected err InvalidProxyLabel for %q, got %v", v, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +149,7 @@ func TestCommaSepProxyLabelSingle(t *testing.T) {
|
|||||||
v := "X-Custom-Header1"
|
v := "X-Custom-Header1"
|
||||||
pl, err := ParseLabel(makeLabel(NSProxy, alias, field), v)
|
pl, err := ParseLabel(makeLabel(NSProxy, alias, field), v)
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
t.Errorf("expected err=nil, got %s", err)
|
t.Errorf("expected err=nil, got %s", err.Error())
|
||||||
}
|
}
|
||||||
if pl.Target != alias {
|
if pl.Target != alias {
|
||||||
t.Errorf("expected alias=%s, got %s", alias, pl.Target)
|
t.Errorf("expected alias=%s, got %s", alias, pl.Target)
|
||||||
@@ -188,7 +173,7 @@ func TestCommaSepProxyLabelMulti(t *testing.T) {
|
|||||||
v := "X-Custom-Header1, X-Custom-Header2,X-Custom-Header3"
|
v := "X-Custom-Header1, X-Custom-Header2,X-Custom-Header3"
|
||||||
pl, err := ParseLabel(makeLabel(NSProxy, alias, field), v)
|
pl, err := ParseLabel(makeLabel(NSProxy, alias, field), v)
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
t.Errorf("expected err=nil, got %s", err)
|
t.Errorf("expected err=nil, got %s", err.Error())
|
||||||
}
|
}
|
||||||
if pl.Target != alias {
|
if pl.Target != alias {
|
||||||
t.Errorf("expected alias=%s, got %s", alias, pl.Target)
|
t.Errorf("expected alias=%s, got %s", alias, pl.Target)
|
||||||
|
|||||||
28
src/error/builder_test.go
Normal file
28
src/error/builder_test.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package error
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestBuilder(t *testing.T) {
|
||||||
|
eb := NewBuilder("error occurred")
|
||||||
|
eb.Add(Failure("Action 1").With(Invalid("Inner", "1")).With(Invalid("Inner", "2")))
|
||||||
|
eb.Add(Failure("Action 2").With(Invalid("Inner", "3")))
|
||||||
|
|
||||||
|
got := eb.Build().Error()
|
||||||
|
expected1 :=
|
||||||
|
(`error occurred:
|
||||||
|
- Action 1 failed:
|
||||||
|
- invalid Inner - 1
|
||||||
|
- invalid Inner - 2
|
||||||
|
- Action 2 failed:
|
||||||
|
- invalid Inner - 3`)
|
||||||
|
expected2 :=
|
||||||
|
(`error occurred:
|
||||||
|
- Action 1 failed:
|
||||||
|
- invalid Inner - 2
|
||||||
|
- invalid Inner - 1
|
||||||
|
- Action 2 failed:
|
||||||
|
- invalid Inner - 3`)
|
||||||
|
if got != expected1 && got != expected2 {
|
||||||
|
t.Errorf("expected \n%s, got \n%s", expected1, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -20,27 +19,24 @@ type (
|
|||||||
// You should return (Slice/Map, NestedError).
|
// You should return (Slice/Map, NestedError).
|
||||||
// Caller then should handle the nested error,
|
// Caller then should handle the nested error,
|
||||||
// and continue with the valid values.
|
// and continue with the valid values.
|
||||||
NestedError struct{ *nestedError }
|
NestedError struct {
|
||||||
nestedError struct {
|
subject string
|
||||||
neBase
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
neBase struct {
|
|
||||||
subject any
|
|
||||||
err error // can be nil
|
err error // can be nil
|
||||||
extras []neBase
|
extras []NestedError
|
||||||
inner *neBase // can be nil
|
|
||||||
level int
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func Nil() NestedError { return NestedError{} }
|
func Nil() NestedError { return NestedError{} }
|
||||||
|
|
||||||
func From(err error) NestedError {
|
func From(err error) NestedError {
|
||||||
if err == nil {
|
switch err := err.(type) {
|
||||||
|
case nil:
|
||||||
return Nil()
|
return Nil()
|
||||||
|
case NestedError:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return NestedError{err: err}
|
||||||
}
|
}
|
||||||
return NestedError{&nestedError{neBase: *copyFrom(err)}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check is a helper function that
|
// Check is a helper function that
|
||||||
@@ -50,73 +46,41 @@ func Check[T any](obj T, err error) (T, NestedError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Join(message string, err ...error) NestedError {
|
func Join(message string, err ...error) NestedError {
|
||||||
extras := make([]neBase, 0, len(err))
|
extras := make([]NestedError, 0, len(err))
|
||||||
nErr := 0
|
nErr := 0
|
||||||
for _, e := range err {
|
for _, e := range err {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
extras = append(extras, *copyFrom(e))
|
extras = append(extras, From(e))
|
||||||
nErr += 1
|
nErr += 1
|
||||||
}
|
}
|
||||||
if nErr == 0 {
|
if nErr == 0 {
|
||||||
return Nil()
|
return Nil()
|
||||||
}
|
}
|
||||||
return NestedError{&nestedError{
|
return NestedError{
|
||||||
neBase: neBase{
|
err: errors.New(message),
|
||||||
err: errors.New(message),
|
extras: extras,
|
||||||
extras: extras,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFrom(err error) *neBase {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
switch base := err.(type) {
|
|
||||||
case *neBase:
|
|
||||||
copy := *base
|
|
||||||
return ©
|
|
||||||
}
|
|
||||||
return &neBase{err: err}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func new(message ...string) NestedError {
|
func (ne NestedError) Error() string {
|
||||||
if len(message) == 0 {
|
|
||||||
return From(nil)
|
|
||||||
}
|
|
||||||
return From(errors.New(strings.Join(message, " ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func errorf(format string, args ...any) NestedError {
|
|
||||||
return From(fmt.Errorf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ne *neBase) Error() string {
|
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
ne.writeToSB(&buf, ne.level, "")
|
ne.writeToSB(&buf, 0, "")
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ne NestedError) ExtraError(err error) NestedError {
|
func (ne NestedError) Is(err error) bool {
|
||||||
if err != nil {
|
return errors.Is(ne.err, err)
|
||||||
ne.Lock()
|
|
||||||
ne.extras = append(ne.extras, From(err).addLevel(ne.Level()+1))
|
|
||||||
ne.Unlock()
|
|
||||||
}
|
|
||||||
return ne
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ne NestedError) Extra(s string) NestedError {
|
func (ne NestedError) With(s any) NestedError {
|
||||||
return ne.ExtraError(errors.New(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ne NestedError) ExtraAny(s any) NestedError {
|
|
||||||
var msg string
|
var msg string
|
||||||
switch ss := s.(type) {
|
switch ss := s.(type) {
|
||||||
|
case nil:
|
||||||
|
return ne
|
||||||
case error:
|
case error:
|
||||||
return ne.ExtraError(ss)
|
return ne.withError(ss)
|
||||||
case string:
|
case string:
|
||||||
msg = ss
|
msg = ss
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
@@ -124,15 +88,22 @@ func (ne NestedError) ExtraAny(s any) NestedError {
|
|||||||
default:
|
default:
|
||||||
msg = fmt.Sprint(s)
|
msg = fmt.Sprint(s)
|
||||||
}
|
}
|
||||||
return ne.ExtraError(errors.New(msg))
|
return ne.withError(errors.New(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ne NestedError) Extraf(format string, args ...any) NestedError {
|
func (ne NestedError) Extraf(format string, args ...any) NestedError {
|
||||||
return ne.ExtraError(fmt.Errorf(format, args...))
|
return ne.With(fmt.Errorf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ne NestedError) Subject(s any) NestedError {
|
func (ne NestedError) Subject(s any) NestedError {
|
||||||
ne.subject = s
|
switch ss := s.(type) {
|
||||||
|
case string:
|
||||||
|
ne.subject = ss
|
||||||
|
case fmt.Stringer:
|
||||||
|
ne.subject = ss.String()
|
||||||
|
default:
|
||||||
|
ne.subject = fmt.Sprint(s)
|
||||||
|
}
|
||||||
return ne
|
return ne
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,74 +114,51 @@ func (ne NestedError) Subjectf(format string, args ...any) NestedError {
|
|||||||
if strings.Contains(format, "%w") {
|
if strings.Contains(format, "%w") {
|
||||||
panic("Subjectf format should not contain %w")
|
panic("Subjectf format should not contain %w")
|
||||||
}
|
}
|
||||||
return ne.Subject(fmt.Sprintf(format, args...))
|
ne.subject = fmt.Sprintf(format, args...)
|
||||||
}
|
|
||||||
|
|
||||||
func (ne NestedError) Level() int {
|
|
||||||
return ne.level
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ne *nestedError) IsNil() bool {
|
|
||||||
return ne == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ne *nestedError) IsNotNil() bool {
|
|
||||||
return ne != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ne NestedError) With(inner error) NestedError {
|
|
||||||
ne.Lock()
|
|
||||||
defer ne.Unlock()
|
|
||||||
|
|
||||||
if ne.inner == nil {
|
|
||||||
ne.inner = copyFrom(inner)
|
|
||||||
} else {
|
|
||||||
ne.ExtraError(inner)
|
|
||||||
}
|
|
||||||
|
|
||||||
root := &ne.neBase
|
|
||||||
for root.inner != nil {
|
|
||||||
root.inner.level = root.level + 1
|
|
||||||
root = root.inner
|
|
||||||
}
|
|
||||||
return ne
|
return ne
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ne *neBase) addLevel(level int) neBase {
|
func (ne NestedError) IsNil() bool {
|
||||||
ret := *ne
|
return ne.err == nil
|
||||||
ret.level += level
|
|
||||||
if ret.inner != nil {
|
|
||||||
inner := ret.inner.addLevel(level)
|
|
||||||
ret.inner = &inner
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ne *neBase) writeToSB(sb *strings.Builder, level int, prefix string) {
|
func (ne NestedError) IsNotNil() bool {
|
||||||
|
return ne.err != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorf(format string, args ...any) NestedError {
|
||||||
|
return From(fmt.Errorf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ne NestedError) withError(err error) NestedError {
|
||||||
|
ne.extras = append(ne.extras, From(err))
|
||||||
|
return ne
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ne *NestedError) writeToSB(sb *strings.Builder, level int, prefix string) {
|
||||||
ne.writeIndents(sb, level)
|
ne.writeIndents(sb, level)
|
||||||
sb.WriteString(prefix)
|
sb.WriteString(prefix)
|
||||||
|
|
||||||
if ne.err != nil {
|
if ne.err != nil {
|
||||||
sb.WriteString(ne.err.Error())
|
sb.WriteString(ne.err.Error())
|
||||||
sb.WriteRune(' ')
|
|
||||||
}
|
}
|
||||||
if ne.subject != nil {
|
if ne.subject != "" {
|
||||||
sb.WriteString(fmt.Sprintf("for %q", ne.subject))
|
if ne.err != nil {
|
||||||
|
sb.WriteString(fmt.Sprintf(" for %q", ne.subject))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprint(ne.subject))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ne.inner != nil || len(ne.extras) > 0 {
|
if len(ne.extras) > 0 {
|
||||||
sb.WriteString(":\n")
|
sb.WriteRune(':')
|
||||||
}
|
for _, extra := range ne.extras {
|
||||||
level += 1
|
sb.WriteRune('\n')
|
||||||
for _, extra := range ne.extras {
|
extra.writeToSB(sb, level+1, "- ")
|
||||||
extra.writeToSB(sb, level, "- ")
|
}
|
||||||
sb.WriteRune('\n')
|
|
||||||
}
|
|
||||||
if ne.inner != nil {
|
|
||||||
ne.inner.writeToSB(sb, level, "- ")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ne *neBase) writeIndents(sb *strings.Builder, level int) {
|
func (ne *NestedError) writeIndents(sb *strings.Builder, level int) {
|
||||||
for i := 0; i < level; i++ {
|
for i := 0; i < level; i++ {
|
||||||
sb.WriteString(" ")
|
sb.WriteString(" ")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,63 +4,67 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AssertEq(t *testing.T, got, want string) {
|
func AssertEq[T comparable](t *testing.T, got, want T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf("expected %q, got %q", want, got)
|
t.Errorf("expected:\n%v, got\n%v", want, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestErrorIs(t *testing.T) {
|
||||||
|
AssertEq(t, Failure("foo").Is(ErrFailure), true)
|
||||||
|
AssertEq(t, Failure("foo").With("bar").Is(ErrFailure), true)
|
||||||
|
AssertEq(t, Failure("foo").With("bar").Is(ErrInvalid), false)
|
||||||
|
AssertEq(t, Failure("foo").With("bar").With("baz").Is(ErrInvalid), false)
|
||||||
|
|
||||||
|
AssertEq(t, Invalid("foo", "bar").Is(ErrInvalid), true)
|
||||||
|
AssertEq(t, Invalid("foo", "bar").Is(ErrFailure), false)
|
||||||
|
}
|
||||||
|
|
||||||
func TestErrorSimple(t *testing.T) {
|
func TestErrorSimple(t *testing.T) {
|
||||||
ne := new("foo bar")
|
ne := Failure("foo bar")
|
||||||
AssertEq(t, ne.Error(), "foo bar")
|
AssertEq(t, ne.Error(), "foo bar failed")
|
||||||
ne.Subject("baz")
|
ne = ne.Subject("baz")
|
||||||
AssertEq(t, ne.Error(), "baz: foo bar")
|
AssertEq(t, ne.Error(), "foo bar failed for \"baz\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorSubjectOnly(t *testing.T) {
|
func TestErrorWith(t *testing.T) {
|
||||||
ne := new().Subject("bar")
|
ne := Failure("foo").With("bar").With("baz")
|
||||||
AssertEq(t, ne.Error(), "bar")
|
AssertEq(t, ne.Error(), "foo failed:\n - bar\n - baz")
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorExtra(t *testing.T) {
|
|
||||||
ne := new("foo").Extra("bar").Extra("baz")
|
|
||||||
AssertEq(t, ne.Error(), "foo:\n - bar\n - baz\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorNested(t *testing.T) {
|
func TestErrorNested(t *testing.T) {
|
||||||
inner := new("inner").
|
inner := Failure("inner").
|
||||||
Extra("123").
|
With("1").
|
||||||
Extra("456")
|
With("1")
|
||||||
inner2 := new("inner").
|
inner2 := Failure("inner2").
|
||||||
Subject("2").
|
Subject("action 2").
|
||||||
Extra("456").
|
With("2").
|
||||||
Extra("789")
|
With("2")
|
||||||
inner3 := new("inner").
|
inner3 := Failure("inner3").
|
||||||
Subject("3").
|
Subject("action 3").
|
||||||
Extra("456").
|
With("3").
|
||||||
Extra("789")
|
With("3")
|
||||||
ne := new("foo").
|
ne := Failure("foo").
|
||||||
Extra("bar").
|
With("bar").
|
||||||
Extra("baz").
|
With("baz").
|
||||||
ExtraError(inner).
|
With(inner).
|
||||||
With(inner.With(inner2.With(inner3)))
|
With(inner.With(inner2.With(inner3)))
|
||||||
want :=
|
want :=
|
||||||
`foo:
|
`foo failed:
|
||||||
- bar
|
- bar
|
||||||
- baz
|
- baz
|
||||||
- inner:
|
- inner failed:
|
||||||
- 123
|
- 1
|
||||||
- 456
|
- 1
|
||||||
- inner:
|
- inner failed:
|
||||||
- 123
|
- 1
|
||||||
- 456
|
- 1
|
||||||
- 2: inner:
|
- inner2 failed for "action 2":
|
||||||
- 456
|
- 2
|
||||||
- 789
|
- 2
|
||||||
- 3: inner:
|
- inner3 failed for "action 3":
|
||||||
- 456
|
- 3
|
||||||
- 789
|
- 3`
|
||||||
`
|
|
||||||
AssertEq(t, ne.Error(), want)
|
AssertEq(t, ne.Error(), want)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
package error
|
package error
|
||||||
|
|
||||||
|
import (
|
||||||
|
stderrors "errors"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrAlreadyStarted = new("already started")
|
ErrFailure = stderrors.New("failed")
|
||||||
ErrNotStarted = new("not started")
|
ErrInvalid = stderrors.New("invalid")
|
||||||
ErrInvalid = new("invalid")
|
ErrUnsupported = stderrors.New("unsupported")
|
||||||
ErrUnsupported = new("unsupported")
|
ErrNotExists = stderrors.New("does not exist")
|
||||||
ErrNotExists = new("does not exist")
|
ErrDuplicated = stderrors.New("duplicated")
|
||||||
ErrDuplicated = new("duplicated")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Failure(what string) NestedError {
|
func Failure(what string) NestedError {
|
||||||
return errorf("%s failed", what)
|
return errorf("%s %w", what, ErrFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Invalid(subject, what any) NestedError {
|
func Invalid(subject, what any) NestedError {
|
||||||
return errorf("%w %s: %q", ErrInvalid, subject, what)
|
return errorf("%w %v - %v", ErrInvalid, subject, what)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Unsupported(subject, what any) NestedError {
|
func Unsupported(subject, what any) NestedError {
|
||||||
return errorf("%w %s: %q", ErrUnsupported, subject, what)
|
return errorf("%w %v - %v", ErrUnsupported, subject, what)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotExists(subject, what any) NestedError {
|
func NotExists(subject, what any) NestedError {
|
||||||
return errorf("%s %w: %q", subject, ErrNotExists, what)
|
return errorf("%s %v - %v", subject, ErrNotExists, what)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Duplicated(subject, what any) NestedError {
|
func Duplicated(subject, what any) NestedError {
|
||||||
return errorf("%w %s: %q", ErrDuplicated, subject, what)
|
return errorf("%w %v: %v", ErrDuplicated, subject, what)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ module github.com/yusing/go-proxy
|
|||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/docker/cli v27.1.1+incompatible
|
github.com/docker/cli v27.1.2+incompatible
|
||||||
github.com/docker/docker v27.1.1+incompatible
|
github.com/docker/docker v27.1.2+incompatible
|
||||||
github.com/fsnotify/fsnotify v1.7.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/go-acme/lego/v4 v4.17.4
|
github.com/go-acme/lego/v4 v4.17.4
|
||||||
github.com/santhosh-tekuri/jsonschema v1.2.4
|
github.com/santhosh-tekuri/jsonschema v1.2.4
|
||||||
@@ -44,7 +44,7 @@ require (
|
|||||||
golang.org/x/crypto v0.26.0 // indirect
|
golang.org/x/crypto v0.26.0 // indirect
|
||||||
golang.org/x/mod v0.20.0 // indirect
|
golang.org/x/mod v0.20.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.23.0 // indirect
|
golang.org/x/sys v0.24.0 // indirect
|
||||||
golang.org/x/text v0.17.0 // indirect
|
golang.org/x/text v0.17.0 // indirect
|
||||||
golang.org/x/time v0.6.0 // indirect
|
golang.org/x/time v0.6.0 // indirect
|
||||||
golang.org/x/tools v0.24.0 // indirect
|
golang.org/x/tools v0.24.0 // indirect
|
||||||
|
|||||||
12
src/go.sum
12
src/go.sum
@@ -13,10 +13,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
|
github.com/docker/cli v27.1.2+incompatible h1:nYviRv5Y+YAKx3dFrTvS1ErkyVVunKOhoweCTE1BsnI=
|
||||||
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v27.1.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
|
github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY=
|
||||||
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
@@ -119,8 +119,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
|
|||||||
71
src/main.go
71
src/main.go
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/common"
|
||||||
"github.com/yusing/go-proxy/config"
|
"github.com/yusing/go-proxy/config"
|
||||||
"github.com/yusing/go-proxy/docker"
|
"github.com/yusing/go-proxy/docker"
|
||||||
|
E "github.com/yusing/go-proxy/error"
|
||||||
R "github.com/yusing/go-proxy/route"
|
R "github.com/yusing/go-proxy/route"
|
||||||
"github.com/yusing/go-proxy/server"
|
"github.com/yusing/go-proxy/server"
|
||||||
F "github.com/yusing/go-proxy/utils/functional"
|
F "github.com/yusing/go-proxy/utils/functional"
|
||||||
@@ -31,19 +32,12 @@ func main() {
|
|||||||
logrus.SetLevel(logrus.DebugLevel)
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
if common.IsRunningAsService {
|
logrus.SetFormatter(&logrus.TextFormatter{
|
||||||
logrus.SetFormatter(&logrus.TextFormatter{
|
DisableSorting: true,
|
||||||
DisableColors: true,
|
FullTimestamp: true,
|
||||||
DisableTimestamp: true,
|
ForceColors: true,
|
||||||
DisableSorting: true,
|
TimestampFormat: "01-02 15:04:05",
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
logrus.SetFormatter(&logrus.TextFormatter{
|
|
||||||
DisableSorting: true,
|
|
||||||
FullTimestamp: true,
|
|
||||||
TimestampFormat: "01-02 15:04:05",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.Command == common.CommandReload {
|
if args.Command == common.CommandReload {
|
||||||
if err := apiUtils.ReloadServer(); err.IsNotNil() {
|
if err := apiUtils.ReloadServer(); err.IsNotNil() {
|
||||||
@@ -53,18 +47,26 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onShutdown := F.NewSlice[func()]()
|
onShutdown := F.NewSlice[func()]()
|
||||||
|
|
||||||
|
// exit if only validate config
|
||||||
|
if args.Command == common.CommandValidate {
|
||||||
|
var err E.NestedError
|
||||||
|
data, err := E.Check(os.ReadFile(common.ConfigPath))
|
||||||
|
if err.IsNotNil() {
|
||||||
|
l.WithError(err).Fatalf("config error")
|
||||||
|
}
|
||||||
|
if err = config.Validate(data); err.IsNotNil() {
|
||||||
|
l.WithError(err).Fatalf("config error")
|
||||||
|
}
|
||||||
|
l.Printf("config OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cfg, err := config.New()
|
cfg, err := config.New()
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
l.Fatalf("config error: %s", err)
|
l.Fatalf("config error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// exit if only validate config
|
|
||||||
// TODO: validate without load
|
|
||||||
if args.Command == common.CommandValidate {
|
|
||||||
l.Printf("config OK")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
onShutdown.Add(func() {
|
onShutdown.Add(func() {
|
||||||
docker.CloseAllClients()
|
docker.CloseAllClients()
|
||||||
cfg.Dispose()
|
cfg.Dispose()
|
||||||
@@ -76,22 +78,27 @@ func main() {
|
|||||||
signal.Notify(sig, syscall.SIGHUP)
|
signal.Notify(sig, syscall.SIGHUP)
|
||||||
|
|
||||||
autocert := cfg.GetAutoCertProvider()
|
autocert := cfg.GetAutoCertProvider()
|
||||||
err = autocert.LoadCert()
|
|
||||||
|
|
||||||
if err.IsNotNil() {
|
if autocert != nil {
|
||||||
l.Infof("error loading certificate: %s\nNow attempting to obtain a new certificate", err)
|
err = autocert.LoadCert()
|
||||||
if err = autocert.ObtainCert(); err.IsNotNil() {
|
|
||||||
ctx, certRenewalCancel := context.WithCancel(context.Background())
|
if err.IsNotNil() {
|
||||||
go autocert.ScheduleRenewal(ctx)
|
l.Error(err)
|
||||||
onShutdown.Add(certRenewalCancel)
|
l.Info("Now attempting to obtain a new certificate...")
|
||||||
|
if err = autocert.ObtainCert(); err.IsNotNil() {
|
||||||
|
ctx, certRenewalCancel := context.WithCancel(context.Background())
|
||||||
|
go autocert.ScheduleRenewal(ctx)
|
||||||
|
onShutdown.Add(certRenewalCancel)
|
||||||
|
} else {
|
||||||
|
l.Warn(err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
l.Warn(err)
|
for name, expiry := range autocert.GetExpiries() {
|
||||||
}
|
l.Infof("certificate %q: expire on %s", name, expiry)
|
||||||
} else {
|
}
|
||||||
for name, expiry := range autocert.GetExpiries() {
|
|
||||||
l.Infof("certificate %q: expire on %s", name, expiry)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyServer := server.InitProxyServer(server.Options{
|
proxyServer := server.InitProxyServer(server.Options{
|
||||||
Name: "proxy",
|
Name: "proxy",
|
||||||
CertProvider: autocert,
|
CertProvider: autocert,
|
||||||
|
|||||||
@@ -40,4 +40,10 @@ func (e *ProxyEntry) SetDefaults() {
|
|||||||
if e.Path == "" {
|
if e.Path == "" {
|
||||||
e.Path = "/"
|
e.Path = "/"
|
||||||
}
|
}
|
||||||
|
switch e.Scheme {
|
||||||
|
case "http":
|
||||||
|
e.Port = "80"
|
||||||
|
case "https":
|
||||||
|
e.Port = "443"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type (
|
|
||||||
ProxyProvider struct {
|
|
||||||
Kind string `json:"kind"` // docker, file
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
ProxyProviders = map[string]ProxyProvider
|
|
||||||
)
|
|
||||||
6
src/models/proxy_providers.go
Normal file
6
src/models/proxy_providers.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type ProxyProviders struct {
|
||||||
|
Files []string `yaml:"include" json:"include"` // docker, file
|
||||||
|
Docker map[string]string `yaml:"docker" json:"docker"`
|
||||||
|
}
|
||||||
@@ -11,5 +11,5 @@ func NewPath(s string) (Path, E.NestedError) {
|
|||||||
if s == "" || s[0] == '/' {
|
if s == "" || s[0] == '/' {
|
||||||
return Path{F.NewStringable(s)}, E.Nil()
|
return Path{F.NewStringable(s)}, E.Nil()
|
||||||
}
|
}
|
||||||
return Path{}, E.Invalid("path", s).Extra("must be empty or start with '/'")
|
return Path{}, E.Invalid("path", s).With("must be empty or start with '/'")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type Port int
|
|||||||
func NewPort(v string) (Port, E.NestedError) {
|
func NewPort(v string) (Port, E.NestedError) {
|
||||||
p, err := strconv.Atoi(v)
|
p, err := strconv.Atoi(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrPort, E.From(err)
|
return ErrPort, E.Invalid("port number", v).With(err)
|
||||||
}
|
}
|
||||||
return NewPortInt(p)
|
return NewPortInt(p)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type StreamPort struct {
|
|||||||
func NewStreamPort(p string) (StreamPort, E.NestedError) {
|
func NewStreamPort(p string) (StreamPort, E.NestedError) {
|
||||||
split := strings.Split(p, ":")
|
split := strings.Split(p, ":")
|
||||||
if len(split) != 2 {
|
if len(split) != 2 {
|
||||||
return StreamPort{}, E.Invalid("stream port", p).Extra("should be in 'x:y' format")
|
return StreamPort{}, E.Invalid("stream port", p).With("should be in 'x:y' format")
|
||||||
}
|
}
|
||||||
|
|
||||||
listeningPort, err := NewPort(split[0])
|
listeningPort, err := NewPort(split[0])
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ type DockerProvider struct {
|
|||||||
dockerHost string
|
dockerHost string
|
||||||
}
|
}
|
||||||
|
|
||||||
func DockerProviderImpl(model *M.ProxyProvider) ProviderImpl {
|
func DockerProviderImpl(dockerHost string) ProviderImpl {
|
||||||
return &DockerProvider{dockerHost: model.Value}
|
return &DockerProvider{dockerHost: dockerHost}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetProxyEntries returns proxy entries from a docker client.
|
// GetProxyEntries returns proxy entries from a docker client.
|
||||||
@@ -32,15 +32,16 @@ func DockerProviderImpl(model *M.ProxyProvider) ProviderImpl {
|
|||||||
// - p: A pointer to the DockerProvider struct.
|
// - p: A pointer to the DockerProvider struct.
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - P.EntryModelSlice: A slice of EntryModel structs representing the proxy entries.
|
// - P.EntryModelSlice: (non-nil) A slice of EntryModel structs representing the proxy entries.
|
||||||
// - error: An error object if there was an error retrieving the docker client information or parsing the labels.
|
// - error: An error object if there was an error retrieving the docker client information or parsing the labels.
|
||||||
func (p DockerProvider) GetProxyEntries() (M.ProxyEntries, E.NestedError) {
|
func (p DockerProvider) GetProxyEntries() (M.ProxyEntries, E.NestedError) {
|
||||||
|
entries := M.NewProxyEntries()
|
||||||
|
|
||||||
info, err := D.GetClientInfo(p.dockerHost)
|
info, err := D.GetClientInfo(p.dockerHost)
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
return nil, E.From(err)
|
return entries, E.From(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries := M.NewProxyEntries()
|
|
||||||
errors := E.NewBuilder("errors when parse docker labels for %q", p.dockerHost)
|
errors := E.NewBuilder("errors when parse docker labels for %q", p.dockerHost)
|
||||||
|
|
||||||
for _, container := range info.Containers {
|
for _, container := range info.Containers {
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ type FileProvider struct {
|
|||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func FileProviderImpl(m *M.ProxyProvider) ProviderImpl {
|
func FileProviderImpl(filename string) ProviderImpl {
|
||||||
return &FileProvider{
|
return &FileProvider{
|
||||||
fileName: m.Value,
|
fileName: filename,
|
||||||
path: path.Join(common.ConfigBasePath, m.Value),
|
path: path.Join(common.ConfigBasePath, filename),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,13 +27,17 @@ func Validate(data []byte) E.NestedError {
|
|||||||
return U.ValidateYaml(U.GetSchema(common.ProvidersSchemaPath), data)
|
return U.ValidateYaml(U.GetSchema(common.ProvidersSchemaPath), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *FileProvider) String() string {
|
||||||
|
return p.fileName
|
||||||
|
}
|
||||||
|
|
||||||
func (p *FileProvider) GetProxyEntries() (M.ProxyEntries, E.NestedError) {
|
func (p *FileProvider) GetProxyEntries() (M.ProxyEntries, E.NestedError) {
|
||||||
entries := M.NewProxyEntries()
|
entries := M.NewProxyEntries()
|
||||||
data, err := E.Check(os.ReadFile(p.path))
|
data, err := E.Check(os.ReadFile(p.path))
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
return entries, E.Failure("read file").Subject(p.fileName).With(err)
|
return entries, E.Failure("read file").Subject(p).With(err)
|
||||||
}
|
}
|
||||||
ne := E.Failure("validation").Subject(p.fileName)
|
ne := E.Failure("validation").Subject(p)
|
||||||
if !common.NoSchemaValidation {
|
if !common.NoSchemaValidation {
|
||||||
if err = Validate(data); err.IsNotNil() {
|
if err = Validate(data); err.IsNotNil() {
|
||||||
return entries, ne.With(err)
|
return entries, ne.With(err)
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package provider
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/yusing/go-proxy/common"
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
M "github.com/yusing/go-proxy/models"
|
M "github.com/yusing/go-proxy/models"
|
||||||
R "github.com/yusing/go-proxy/route"
|
R "github.com/yusing/go-proxy/route"
|
||||||
@@ -20,6 +21,7 @@ type Provider struct {
|
|||||||
ProviderImpl
|
ProviderImpl
|
||||||
|
|
||||||
name string
|
name string
|
||||||
|
t ProviderType
|
||||||
routes *R.Routes
|
routes *R.Routes
|
||||||
reloadReqCh chan struct{}
|
reloadReqCh chan struct{}
|
||||||
|
|
||||||
@@ -30,27 +32,49 @@ type Provider struct {
|
|||||||
l *logrus.Entry
|
l *logrus.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProvider(name string, model M.ProxyProvider) (p *Provider) {
|
type ProviderType string
|
||||||
p = &Provider{
|
|
||||||
|
const (
|
||||||
|
ProviderTypeDocker ProviderType = "docker"
|
||||||
|
ProviderTypeFile ProviderType = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newProvider(name string, t ProviderType) *Provider {
|
||||||
|
return &Provider{
|
||||||
name: name,
|
name: name,
|
||||||
|
t: t,
|
||||||
routes: R.NewRoutes(),
|
routes: R.NewRoutes(),
|
||||||
reloadReqCh: make(chan struct{}, 1),
|
reloadReqCh: make(chan struct{}, 1),
|
||||||
l: logrus.WithField("provider", name),
|
l: logrus.WithField("provider", name),
|
||||||
}
|
}
|
||||||
switch model.Kind {
|
}
|
||||||
case common.ProviderKind_Docker:
|
func NewFileProvider(filename string) *Provider {
|
||||||
p.ProviderImpl = DockerProviderImpl(&model)
|
name := path.Base(filename)
|
||||||
case common.ProviderKind_File:
|
p := newProvider(name, ProviderTypeFile)
|
||||||
p.ProviderImpl = FileProviderImpl(&model)
|
p.ProviderImpl = FileProviderImpl(filename)
|
||||||
}
|
|
||||||
p.watcher = p.NewWatcher()
|
p.watcher = p.NewWatcher()
|
||||||
return
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDockerProvider(name string, dockerHost string) *Provider {
|
||||||
|
p := newProvider(name, ProviderTypeDocker)
|
||||||
|
p.ProviderImpl = DockerProviderImpl(dockerHost)
|
||||||
|
p.watcher = p.NewWatcher()
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) GetName() string {
|
func (p *Provider) GetName() string {
|
||||||
return p.name
|
return p.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) GetType() ProviderType {
|
||||||
|
return p.t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) String() string {
|
||||||
|
return fmt.Sprintf("%s (%s provider)", p.name, p.t)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) StartAllRoutes() E.NestedError {
|
func (p *Provider) StartAllRoutes() E.NestedError {
|
||||||
err := p.loadRoutes()
|
err := p.loadRoutes()
|
||||||
|
|
||||||
@@ -58,23 +82,24 @@ func (p *Provider) StartAllRoutes() E.NestedError {
|
|||||||
p.watcherCtx, p.watcherCancel = context.WithCancel(context.Background())
|
p.watcherCtx, p.watcherCancel = context.WithCancel(context.Background())
|
||||||
go p.watchEvents()
|
go p.watchEvents()
|
||||||
|
|
||||||
if err.IsNotNil() {
|
errors := E.NewBuilder("errors in routes")
|
||||||
return err
|
|
||||||
}
|
|
||||||
errors := E.NewBuilder("errors starting routes for provider %q", p.name)
|
|
||||||
nStarted := 0
|
nStarted := 0
|
||||||
|
nFailed := 0
|
||||||
|
|
||||||
|
if err.IsNotNil() {
|
||||||
|
errors.Add(err)
|
||||||
|
}
|
||||||
|
|
||||||
p.routes.EachKVParallel(func(alias string, r R.Route) {
|
p.routes.EachKVParallel(func(alias string, r R.Route) {
|
||||||
if err := r.Start(); err.IsNotNil() {
|
if err := r.Start(); err.IsNotNil() {
|
||||||
errors.Add(err.Subject(alias))
|
errors.Add(err.Subject(r))
|
||||||
|
nFailed++
|
||||||
} else {
|
} else {
|
||||||
nStarted++
|
nStarted++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if err := errors.Build(); err.IsNotNil() {
|
p.l.Infof("%d routes started, %d failed", nStarted, nFailed)
|
||||||
return err
|
return errors.Build()
|
||||||
}
|
|
||||||
p.l.Infof("%d routes started", nStarted)
|
|
||||||
return E.Nil()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) StopAllRoutes() E.NestedError {
|
func (p *Provider) StopAllRoutes() E.NestedError {
|
||||||
@@ -87,7 +112,7 @@ func (p *Provider) StopAllRoutes() E.NestedError {
|
|||||||
nStopped := 0
|
nStopped := 0
|
||||||
p.routes.EachKVParallel(func(alias string, r R.Route) {
|
p.routes.EachKVParallel(func(alias string, r R.Route) {
|
||||||
if err := r.Stop(); err.IsNotNil() {
|
if err := r.Stop(); err.IsNotNil() {
|
||||||
errors.Add(err.Subject(alias))
|
errors.Add(err.Subject(r))
|
||||||
} else {
|
} else {
|
||||||
nStopped++
|
nStopped++
|
||||||
}
|
}
|
||||||
@@ -146,7 +171,7 @@ func (p *Provider) loadRoutes() E.NestedError {
|
|||||||
entries, err := p.GetProxyEntries()
|
entries, err := p.GetProxyEntries()
|
||||||
|
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
p.l.Warn(err.Subjectf("provider %s", p.name))
|
p.l.Warn(err.Subject(p))
|
||||||
}
|
}
|
||||||
p.routes = R.NewRoutes()
|
p.routes = R.NewRoutes()
|
||||||
|
|
||||||
@@ -155,7 +180,7 @@ func (p *Provider) loadRoutes() E.NestedError {
|
|||||||
e.Alias = a
|
e.Alias = a
|
||||||
r, err := R.NewRoute(e)
|
r, err := R.NewRoute(e)
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
errors.Addf("%s: %w", a, err)
|
errors.Add(err.Subject(a))
|
||||||
p.l.Debugf("failed to load route: %s, %s", a, err)
|
p.l.Debugf("failed to load route: %s, %s", a, err)
|
||||||
} else {
|
} else {
|
||||||
p.routes.Set(a, r)
|
p.routes.Set(a, r)
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ func NewHTTPRoute(entry *P.Entry) (*HTTPRoute, E.NestedError) {
|
|||||||
path := entry.Path.String()
|
path := entry.Path.String()
|
||||||
if _, exists := r.Subroutes[path]; exists {
|
if _, exists := r.Subroutes[path]; exists {
|
||||||
httpRoutes.Unlock()
|
httpRoutes.Unlock()
|
||||||
return nil, E.Duplicated("path", path).Subject(entry.Alias)
|
return nil, E.Duplicated("path", path)
|
||||||
}
|
}
|
||||||
r.mux.HandleFunc(path, rp.ServeHTTP)
|
r.mux.HandleFunc(path, rp.ServeHTTP)
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
@@ -75,9 +75,9 @@ func NewHTTPRoute(entry *P.Entry) (*HTTPRoute, E.NestedError) {
|
|||||||
switch t := err.(type) {
|
switch t := err.(type) {
|
||||||
case error:
|
case error:
|
||||||
// NOTE: likely path pattern error
|
// NOTE: likely path pattern error
|
||||||
return nil, E.From(t).Subject(entry.Alias)
|
return nil, E.From(t)
|
||||||
default:
|
default:
|
||||||
return nil, E.From(fmt.Errorf("%v", t)).Subject(entry.Alias)
|
return nil, E.From(fmt.Errorf("%v", t))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +106,10 @@ func NewHTTPRoute(entry *P.Entry) (*HTTPRoute, E.NestedError) {
|
|||||||
return r, E.Nil()
|
return r, E.Nil()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *HTTPRoute) String() string {
|
||||||
|
return fmt.Sprintf("%s (reverse proxy)", r.Alias)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *HTTPRoute) Start() E.NestedError {
|
func (r *HTTPRoute) Start() E.NestedError {
|
||||||
httpRoutes.Set(r.Alias.String(), r)
|
httpRoutes.Set(r.Alias.String(), r)
|
||||||
return E.Nil()
|
return E.Nil()
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type (
|
|||||||
Route interface {
|
Route interface {
|
||||||
Start() E.NestedError
|
Start() E.NestedError
|
||||||
Stop() E.NestedError
|
Stop() E.NestedError
|
||||||
|
String() string
|
||||||
}
|
}
|
||||||
Routes = F.Map[string, Route]
|
Routes = F.Map[string, Route]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,9 +49,13 @@ func NewStreamRoute(entry *P.StreamEntry) (*StreamRoute, E.NestedError) {
|
|||||||
return base, E.Nil()
|
return base, E.Nil()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *StreamRoute) String() string {
|
||||||
|
return fmt.Sprintf("%s (%v stream)", r.Alias, r.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *StreamRoute) Start() E.NestedError {
|
func (r *StreamRoute) Start() E.NestedError {
|
||||||
if r.started.Load() {
|
if r.started.Load() {
|
||||||
return E.ErrAlreadyStarted
|
return E.Invalid("state", "already started")
|
||||||
}
|
}
|
||||||
r.wg.Wait()
|
r.wg.Wait()
|
||||||
if err := r.Setup(); err != nil {
|
if err := r.Setup(); err != nil {
|
||||||
@@ -66,7 +70,7 @@ func (r *StreamRoute) Start() E.NestedError {
|
|||||||
|
|
||||||
func (r *StreamRoute) Stop() E.NestedError {
|
func (r *StreamRoute) Stop() E.NestedError {
|
||||||
if !r.started.Load() {
|
if !r.started.Load() {
|
||||||
return E.ErrNotStarted
|
return E.Invalid("state", "not started")
|
||||||
}
|
}
|
||||||
l := r.l
|
l := r.l
|
||||||
close(r.stopCh)
|
close(r.stopCh)
|
||||||
|
|||||||
@@ -49,8 +49,12 @@ func NewServer(opt Options) (s *server) {
|
|||||||
logrus.WithFields(logrus.Fields{"?": "server", "name": opt.Name}),
|
logrus.WithFields(logrus.Fields{"?": "server", "name": opt.Name}),
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err := opt.CertProvider.GetCert(nil)
|
certAvailable := false
|
||||||
certAvailable := err == nil
|
if opt.CertProvider != nil {
|
||||||
|
_, err := opt.CertProvider.GetCert(nil)
|
||||||
|
certAvailable = err == nil
|
||||||
|
}
|
||||||
|
|
||||||
if certAvailable && opt.RedirectToHTTPS && opt.HTTPSPort != "" {
|
if certAvailable && opt.RedirectToHTTPS && opt.HTTPSPort != "" {
|
||||||
httpHandler = redirectToTLSHandler(opt.HTTPSPort)
|
httpHandler = redirectToTLSHandler(opt.HTTPSPort)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -37,11 +37,13 @@ func (s *Slice[T]) Set(i int, v T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slice[T]) Add(e T) *Slice[T] {
|
func (s *Slice[T]) Add(e T) *Slice[T] {
|
||||||
return &Slice[T]{append(s.s, e)}
|
s.s = append(s.s, e)
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slice[T]) AddRange(other *Slice[T]) *Slice[T] {
|
func (s *Slice[T]) AddRange(other *Slice[T]) *Slice[T] {
|
||||||
return &Slice[T]{append(s.s, other.s...)}
|
s.s = append(s.s, other.s...)
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slice[T]) ForEach(do func(T)) {
|
func (s *Slice[T]) ForEach(do func(T)) {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.5.0-beta
|
0.5.0-beta2
|
||||||
Reference in New Issue
Block a user