From 2cec88d3ce0de2696a8925bb77924a18b4889779 Mon Sep 17 00:00:00 2001 From: vSLY <39321074+vsly-ru@users.noreply.github.com> Date: Sun, 4 May 2025 13:45:29 -0300 Subject: [PATCH 01/13] Update README.md (#104) Clarify setup process --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c60c54b..59b9366 100755 --- a/README.md +++ b/README.md @@ -101,7 +101,13 @@ Configure Wildcard DNS Record(s) to point to machine running `GoDoxy`, e.g. /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/yusing/godoxy/main/scripts/setup.sh)" ``` -3. You may now do some extra configuration on WebUI `https://godoxy.yourdomain.com` +3. Start the docker compose service from generated `compose.yml`: + + ```shell + docker compose up -d + ``` + +4. You may now do some extra configuration on WebUI `https://godoxy.yourdomain.com` ## How does GoDoxy work From c55c6c84bce584f48a2dadc9a4afcb62eeccb03b Mon Sep 17 00:00:00 2001 From: yusing Date: Mon, 5 May 2025 13:27:00 +0800 Subject: [PATCH 02/13] feat(health): add health check detail to health api --- internal/idlewatcher/health.go | 15 +++++++++++++++ internal/net/gphttp/loadbalancer/loadbalancer.go | 6 ++++++ internal/route/routes/query.go | 2 ++ internal/watcher/health/monitor/monitor.go | 9 +++++++++ internal/watcher/health/types.go | 1 + 5 files changed, 33 insertions(+) diff --git a/internal/idlewatcher/health.go b/internal/idlewatcher/health.go index 5a256a4..8e3a8c6 100644 --- a/internal/idlewatcher/health.go +++ b/internal/idlewatcher/health.go @@ -65,6 +65,21 @@ func (w *Watcher) Status() health.Status { return health.StatusNapping } +// Detail implements health.HealthMonitor. +func (w *Watcher) Detail() string { + state := w.state.Load() + if state.err != nil { + return state.err.Error() + } + if !state.ready { + return "not ready" + } + if state.status == idlewatcher.ContainerStatusRunning { + return "starting" + } + return "napping" +} + func checkUpdateState(key string) (w *Watcher, ready bool, err error) { watcherMapMu.RLock() w, ok := watcherMap[key] diff --git a/internal/net/gphttp/loadbalancer/loadbalancer.go b/internal/net/gphttp/loadbalancer/loadbalancer.go index 972bbec..d36bf98 100644 --- a/internal/net/gphttp/loadbalancer/loadbalancer.go +++ b/internal/net/gphttp/loadbalancer/loadbalancer.go @@ -266,6 +266,12 @@ func (lb *LoadBalancer) Status() health.Status { return status } +// Detail implements health.HealthMonitor. +func (lb *LoadBalancer) Detail() string { + _, numHealthy := lb.status() + return fmt.Sprintf("%d/%d servers are healthy", numHealthy, lb.pool.Size()) +} + func (lb *LoadBalancer) status() (status health.Status, numHealthy int) { if lb.pool.Size() == 0 { return health.StatusUnknown, 0 diff --git a/internal/route/routes/query.go b/internal/route/routes/query.go index 3ba7088..44a0264 100644 --- a/internal/route/routes/query.go +++ b/internal/route/routes/query.go @@ -16,12 +16,14 @@ func getHealthInfo(r Route) map[string]string { "status": "unknown", "uptime": "n/a", "latency": "n/a", + "detail": "n/a", } } return map[string]string{ "status": mon.Status().String(), "uptime": mon.Uptime().Round(time.Second).String(), "latency": mon.Latency().Round(time.Microsecond).String(), + "detail": mon.Detail(), } } diff --git a/internal/watcher/health/monitor/monitor.go b/internal/watcher/health/monitor/monitor.go index 8f4ddf3..20bc8ed 100644 --- a/internal/watcher/health/monitor/monitor.go +++ b/internal/watcher/health/monitor/monitor.go @@ -168,6 +168,15 @@ func (mon *monitor) Latency() time.Duration { return res.Latency } +// Detail implements HealthMonitor. +func (mon *monitor) Detail() string { + res := mon.lastResult.Load() + if res == nil { + return "" + } + return res.Detail +} + // Name implements HealthMonitor. func (mon *monitor) Name() string { parts := strutils.SplitRune(mon.service, '/') diff --git a/internal/watcher/health/types.go b/internal/watcher/health/types.go index 0f6610e..0d245cd 100644 --- a/internal/watcher/health/types.go +++ b/internal/watcher/health/types.go @@ -19,6 +19,7 @@ type ( Status() Status Uptime() time.Duration Latency() time.Duration + Detail() string } HealthMonitor interface { task.TaskStarter From aa23b5b595c82d0639f477c3e9fdc58ba0710a49 Mon Sep 17 00:00:00 2001 From: yusing Date: Mon, 5 May 2025 13:27:51 +0800 Subject: [PATCH 03/13] test: add unit tests for FormatByteSize function --- internal/utils/strutils/format_test.go | 81 ++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/internal/utils/strutils/format_test.go b/internal/utils/strutils/format_test.go index 5fa5e6b..ce2fa37 100644 --- a/internal/utils/strutils/format_test.go +++ b/internal/utils/strutils/format_test.go @@ -213,3 +213,84 @@ func TestFormatLastSeen(t *testing.T) { }) } } + +func TestFormatByteSize(t *testing.T) { + tests := []struct { + name string + size int64 + expected string + }{ + { + name: "zero size", + size: 0, + expected: "0 B", + }, + { + name: "one byte", + size: 1, + expected: "1 B", + }, + { + name: "bytes (less than 1 KiB)", + size: 1023, + expected: "1023 B", + }, + { + name: "1 KiB", + size: 1024, + expected: "1 KiB", + }, + { + name: "KiB (less than 1 MiB)", + size: 1024 * 1023, + expected: "1023 KiB", + }, + { + name: "1 MiB", + size: 1024 * 1024, + expected: "1 MiB", + }, + { + name: "MiB (less than 1 GiB)", + size: 1024 * 1024 * 1023, + expected: "1023 MiB", + }, + { + name: "1 GiB", + size: 1024 * 1024 * 1024, + expected: "1 GiB", + }, + { + name: "GiB (less than 1 TiB)", + size: 1024 * 1024 * 1024 * 1023, + expected: "1023 GiB", + }, + { + name: "1 TiB", + size: 1024 * 1024 * 1024 * 1024, + expected: "1 TiB", + }, + { + name: "TiB (less than 1 PiB)", + size: 1024 * 1024 * 1024 * 1024 * 1023, + expected: "1023 TiB", + }, + { + name: "1 PiB", + size: 1024 * 1024 * 1024 * 1024 * 1024, + expected: "1 PiB", + }, + { + name: "PiB (large number)", + size: 1024 * 1024 * 1024 * 1024 * 1024 * 1023, + expected: "1023 PiB", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := FormatByteSize(tt.size) + expect.Equal(t, result, tt.expected) + }) + } +} From dd65a8d04bcbdb92d0284e37b560aa430e2273d4 Mon Sep 17 00:00:00 2001 From: yusing Date: Mon, 5 May 2025 13:36:08 +0800 Subject: [PATCH 04/13] style: replace for loops with slices.Contains --- internal/route/rules/on.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/internal/route/rules/on.go b/internal/route/rules/on.go index 8c9e29f..8972fa9 100644 --- a/internal/route/rules/on.go +++ b/internal/route/rules/on.go @@ -3,6 +3,8 @@ package rules import ( "net/http" + "slices" + "github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/net/types" "github.com/yusing/go-proxy/internal/utils/strutils" @@ -47,12 +49,7 @@ var checkers = map[string]struct { } } return func(cached Cache, r *http.Request) bool { - for _, vv := range r.Header[k] { - if v == vv { - return true - } - } - return false + return slices.Contains(r.Header[k], v) } }, }, @@ -74,12 +71,7 @@ var checkers = map[string]struct { } return func(cached Cache, r *http.Request) bool { queries := cached.GetQueries(r)[k] - for _, query := range queries { - if query == v { - return true - } - } - return false + return slices.Contains(queries, v) } }, }, From ef956821167179c18d971218b5ae4535aad7c7a6 Mon Sep 17 00:00:00 2001 From: yusing Date: Mon, 5 May 2025 14:42:55 +0800 Subject: [PATCH 05/13] feat(rules): compile path rules directly to glob --- internal/route/rules/on.go | 7 ++++--- internal/route/rules/validate.go | 18 +++++++++++++++++- internal/utils/strutils/glob.go | 28 ---------------------------- 3 files changed, 21 insertions(+), 32 deletions(-) delete mode 100644 internal/utils/strutils/glob.go diff --git a/internal/route/rules/on.go b/internal/route/rules/on.go index 8972fa9..7793e2e 100644 --- a/internal/route/rules/on.go +++ b/internal/route/rules/on.go @@ -5,6 +5,7 @@ import ( "slices" + "github.com/gobwas/glob" "github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/net/types" "github.com/yusing/go-proxy/internal/utils/strutils" @@ -176,15 +177,15 @@ var checkers = map[string]struct { "path": "the request path", }, }, - validate: validateURLPath, + validate: validateURLPathGlob, builder: func(args any) CheckFunc { - pat := args.(string) + pat := args.(glob.Glob) return func(cached Cache, r *http.Request) bool { reqPath := r.URL.Path if len(reqPath) > 0 && reqPath[0] != '/' { reqPath = "/" + reqPath } - return strutils.GlobMatch(pat, reqPath) + return pat.Match(reqPath) } }, }, diff --git a/internal/route/rules/validate.go b/internal/route/rules/validate.go index 58a7dd8..7f45f10 100644 --- a/internal/route/rules/validate.go +++ b/internal/route/rules/validate.go @@ -4,8 +4,10 @@ import ( "fmt" "os" "path" + "path/filepath" "strings" + "github.com/gobwas/glob" "github.com/yusing/go-proxy/internal/gperr" gphttp "github.com/yusing/go-proxy/internal/net/gphttp" "github.com/yusing/go-proxy/internal/net/types" @@ -111,6 +113,20 @@ func validateURLPath(args []string) (any, gperr.Error) { return p, nil } +// validateURLPathGlob returns []string with each element validated. +func validateURLPathGlob(args []string) (any, gperr.Error) { + p, err := validateURLPath(args) + if err != nil { + return nil, err + } + + g, gErr := glob.Compile(p.(string)) + if gErr != nil { + return nil, ErrInvalidArguments.With(gErr) + } + return g, nil +} + // validateURLPaths returns []string with each element validated. func validateURLPaths(paths []string) (any, gperr.Error) { errs := gperr.NewBuilder("invalid url paths") @@ -133,7 +149,7 @@ func validateFSPath(args []string) (any, gperr.Error) { if len(args) != 1 { return nil, ErrExpectOneArg } - p := path.Clean(args[0]) + p := filepath.Clean(args[0]) if _, err := os.Stat(p); err != nil { return nil, ErrInvalidArguments.With(err) } diff --git a/internal/utils/strutils/glob.go b/internal/utils/strutils/glob.go deleted file mode 100644 index 6b8047d..0000000 --- a/internal/utils/strutils/glob.go +++ /dev/null @@ -1,28 +0,0 @@ -package strutils - -import ( - "sync" - - "github.com/gobwas/glob" -) - -var ( - globPatterns = make(map[string]glob.Glob) - globPatternsMu sync.Mutex -) - -func GlobMatch(pattern string, s string) bool { - if glob, ok := globPatterns[pattern]; ok { - return glob.Match(s) - } - - globPatternsMu.Lock() - defer globPatternsMu.Unlock() - - glob, err := glob.Compile(pattern) - if err != nil { - return false - } - globPatterns[pattern] = glob - return glob.Match(s) -} From ddab2766b4aefec9ee37b6ce6baa1af4d4655bec Mon Sep 17 00:00:00 2001 From: yusing Date: Mon, 5 May 2025 18:01:07 +0800 Subject: [PATCH 06/13] feat(middlewares): middleware bypass rules --- internal/net/gphttp/middleware/bypass.go | 53 +++++++ internal/net/gphttp/middleware/bypass_test.go | 131 ++++++++++++++++++ internal/net/gphttp/middleware/middleware.go | 52 ++++--- internal/route/rules/on.go | 4 + 4 files changed, 221 insertions(+), 19 deletions(-) create mode 100644 internal/net/gphttp/middleware/bypass.go create mode 100644 internal/net/gphttp/middleware/bypass_test.go diff --git a/internal/net/gphttp/middleware/bypass.go b/internal/net/gphttp/middleware/bypass.go new file mode 100644 index 0000000..a5f1b0a --- /dev/null +++ b/internal/net/gphttp/middleware/bypass.go @@ -0,0 +1,53 @@ +package middleware + +import ( + "net/http" + + "github.com/yusing/go-proxy/internal/route/rules" +) + +type Bypass []rules.RuleOn + +func (b Bypass) ShouldBypass(r *http.Request) bool { + cached := rules.NewCache() + defer cached.Release() + for _, rule := range b { + if rule.Check(cached, r) { + return true + } + } + return false +} + +type checkBypass struct { + bypass Bypass + modReq RequestModifier + modRes ResponseModifier +} + +func (c *checkBypass) before(w http.ResponseWriter, r *http.Request) (proceedNext bool) { + if c.modReq == nil || c.bypass.ShouldBypass(r) { + return true + } + return c.modReq.before(w, r) +} + +func (c *checkBypass) modifyResponse(resp *http.Response) error { + if c.modRes == nil || c.bypass.ShouldBypass(resp.Request) { + return nil + } + return c.modRes.modifyResponse(resp) +} + +func (m *Middleware) withCheckBypass() any { + if len(m.Bypass) > 0 { + modReq, _ := m.impl.(RequestModifier) + modRes, _ := m.impl.(ResponseModifier) + return &checkBypass{ + bypass: m.Bypass, + modReq: modReq, + modRes: modRes, + } + } + return m.impl +} diff --git a/internal/net/gphttp/middleware/bypass_test.go b/internal/net/gphttp/middleware/bypass_test.go new file mode 100644 index 0000000..728b866 --- /dev/null +++ b/internal/net/gphttp/middleware/bypass_test.go @@ -0,0 +1,131 @@ +package middleware_test + +import ( + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + . "github.com/yusing/go-proxy/internal/net/gphttp/middleware" + "github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy" + "github.com/yusing/go-proxy/internal/net/types" + expect "github.com/yusing/go-proxy/internal/utils/testing" +) + +func noOpHandler(w http.ResponseWriter, r *http.Request) {} + +func TestBypassCIDR(t *testing.T) { + mr, err := ModifyRequest.New(map[string]any{ + "set_headers": map[string]string{ + "Test-Header": "test-value", + }, + "bypass": []string{"remote 127.0.0.1/32"}, + }) + expect.NoError(t, err) + + tests := []struct { + name string + remoteAddr string + expectBypass bool + }{ + {"bypass", "127.0.0.1:8080", true}, + {"no_bypass", "192.168.1.1:8080", false}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := httptest.NewRequest("GET", "http://example.com", nil) + req.RemoteAddr = test.remoteAddr + recorder := httptest.NewRecorder() + mr.ModifyRequest(noOpHandler, recorder, req) + expect.NoError(t, err) + if test.expectBypass { + expect.Equal(t, req.Header.Get("Test-Header"), "") + } else { + expect.Equal(t, req.Header.Get("Test-Header"), "test-value") + } + }) + } +} + +func TestBypassPath(t *testing.T) { + mr, err := ModifyRequest.New(map[string]any{ + "bypass": []string{"path /test/*", "path /api"}, + "set_headers": map[string]string{ + "Test-Header": "test-value", + }, + }) + expect.NoError(t, err) + + tests := []struct { + name string + path string + expectBypass bool + }{ + {"bypass", "/test/123", true}, + {"bypass2", "/test/123/456", true}, + {"bypass3", "/api", true}, + {"no_bypass", "/test1/123/456", false}, + {"no_bypass2", "/api/123", false}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := httptest.NewRequest("GET", "http://example.com"+test.path, nil) + recorder := httptest.NewRecorder() + mr.ModifyRequest(noOpHandler, recorder, req) + expect.NoError(t, err) + if test.expectBypass { + expect.Equal(t, req.Header.Get("Test-Header"), "") + } else { + expect.Equal(t, req.Header.Get("Test-Header"), "test-value") + } + }) + } +} + +type fakeRoundTripper struct{} + +func (f fakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader("")), + Request: req, + Header: make(http.Header), + }, nil +} + +func TestReverseProxyBypass(t *testing.T) { + rp := reverseproxy.NewReverseProxy("test", types.MustParseURL("http://example.com"), fakeRoundTripper{}) + err := PatchReverseProxy(rp, map[string]OptionsRaw{ + "response": { + "bypass": "path /test/* | path /api", + "set_headers": map[string]string{ + "Test-Header": "test-value", + }, + }, + }) + expect.NoError(t, err) + tests := []struct { + name string + path string + expectBypass bool + }{ + {"bypass", "/test/123", true}, + {"bypass2", "/test/123/456", true}, + {"bypass3", "/api", true}, + {"no_bypass", "/test1/123/456", false}, + {"no_bypass2", "/api/123", false}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := httptest.NewRequest("GET", "http://example.com"+test.path, nil) + recorder := httptest.NewRecorder() + rp.ServeHTTP(recorder, req) + if test.expectBypass { + expect.Equal(t, recorder.Header().Get("Test-Header"), "") + } else { + expect.Equal(t, recorder.Header().Get("Test-Header"), "test-value") + } + }) + } +} diff --git a/internal/net/gphttp/middleware/middleware.go b/internal/net/gphttp/middleware/middleware.go index c6b5fd8..a39ffda 100644 --- a/internal/net/gphttp/middleware/middleware.go +++ b/internal/net/gphttp/middleware/middleware.go @@ -2,6 +2,7 @@ package middleware import ( "encoding/json" + "maps" "net/http" "reflect" "sort" @@ -23,16 +24,22 @@ type ( ImplNewFunc = func() any OptionsRaw = map[string]any - Middleware struct { - name string - construct ImplNewFunc - impl any + commonOptions = struct { // priority is only applied for ReverseProxy. // // Middleware compose follows the order of the slice // // Default is 10, 0 is the highest - priority int + Priority int `json:"priority"` + Bypass Bypass `json:"bypass"` + } + + Middleware struct { + name string + construct ImplNewFunc + impl any + + commonOptions } ByPriority []*Middleware @@ -55,7 +62,7 @@ const DefaultPriority = 10 func (m ByPriority) Len() int { return len(m) } func (m ByPriority) Swap(i, j int) { m[i], m[j] = m[j], m[i] } -func (m ByPriority) Less(i, j int) bool { return m[i].priority < m[j].priority } +func (m ByPriority) Less(i, j int) bool { return m[i].Priority < m[j].Priority } func NewMiddleware[ImplType any]() *Middleware { // type check @@ -107,21 +114,22 @@ func (m *Middleware) apply(optsRaw OptionsRaw) gperr.Error { if len(optsRaw) == 0 { return nil } - priority, ok := optsRaw["priority"].(int) - if ok { - m.priority = priority - // remove priority for deserialization, restore later - delete(optsRaw, "priority") - defer func() { - optsRaw["priority"] = priority - }() - } else { - m.priority = DefaultPriority + commonOpts := map[string]any{ + "priority": optsRaw["priority"], + "bypass": optsRaw["bypass"], + } + if err := utils.MapUnmarshalValidate(commonOpts, &m.commonOptions); err != nil { + return err + } + optsRaw = maps.Clone(optsRaw) + for k := range commonOpts { + delete(optsRaw, k) } return utils.MapUnmarshalValidate(optsRaw, m.impl) } func (m *Middleware) finalize() error { + m.impl = m.withCheckBypass() if finalizer, ok := m.impl.(MiddlewareFinalizer); ok { finalizer.finalize() return nil @@ -159,10 +167,16 @@ func (m *Middleware) String() string { } func (m *Middleware) MarshalJSON() ([]byte, error) { + type allOptions struct { + commonOptions + any + } return json.MarshalIndent(map[string]any{ - "name": m.name, - "options": m.impl, - "priority": m.priority, + "name": m.name, + "options": allOptions{ + commonOptions: m.commonOptions, + any: m.impl, + }, }, "", " ") } diff --git a/internal/route/rules/on.go b/internal/route/rules/on.go index 7793e2e..2c427a7 100644 --- a/internal/route/rules/on.go +++ b/internal/route/rules/on.go @@ -16,6 +16,10 @@ type RuleOn struct { checker Checker } +func (on *RuleOn) Check(cached Cache, r *http.Request) bool { + return on.checker.Check(cached, r) +} + const ( OnHeader = "header" OnQuery = "query" From 4415bffc3580c59bcfe644a2d01dd750f6ffcec3 Mon Sep 17 00:00:00 2001 From: yusing Date: Mon, 5 May 2025 19:15:35 +0800 Subject: [PATCH 07/13] feat(rules.on): support & as logical AND --- internal/route/rules/on.go | 72 ++++++++++++++++++++++++++++++--- internal/route/rules/on_test.go | 55 +++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 5 deletions(-) diff --git a/internal/route/rules/on.go b/internal/route/rules/on.go index 2c427a7..61cc752 100644 --- a/internal/route/rules/on.go +++ b/internal/route/rules/on.go @@ -2,6 +2,7 @@ package rules import ( "net/http" + "strings" "slices" @@ -230,19 +231,80 @@ var checkers = map[string]struct { }, } +var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1} +var andSeps = [256]uint8{'&': 1, '\n': 1} + +func indexAnd(s string) int { + for i, c := range s { + if andSeps[c] != 0 { + return i + } + } + return -1 +} + +func countAnd(s string) int { + n := 0 + for _, c := range s { + if andSeps[c] != 0 { + n++ + } + } + return n +} + +// splitAnd splits a string by "&" and "\n" with all spaces removed. +// empty strings are not included in the result. +func splitAnd(s string) []string { + if s == "" { + return []string{} + } + n := countAnd(s) + a := make([]string, n+1) + i := 0 + for i < n { + end := indexAnd(s) + if end == -1 { + break + } + beg := 0 + // trim leading spaces + for beg < end && asciiSpace[s[beg]] != 0 { + beg++ + } + // trim trailing spaces + next := end + 1 + for end-1 > beg && asciiSpace[s[end-1]] != 0 { + end-- + } + // skip empty segments + if end > beg { + a[i] = s[beg:end] + i++ + } + s = s[next:] + } + s = strings.TrimSpace(s) + if s != "" { + a[i] = s + i++ + } + return a[:i] +} + // Parse implements strutils.Parser. func (on *RuleOn) Parse(v string) error { on.raw = v - lines := strutils.SplitLine(v) - checkAnd := make(CheckMatchAll, 0, len(lines)) + rules := splitAnd(v) + checkAnd := make(CheckMatchAll, 0, len(rules)) errs := gperr.NewBuilder("rule.on syntax errors") - for i, line := range lines { - if line == "" { + for i, rule := range rules { + if rule == "" { continue } - parsed, err := parseOn(line) + parsed, err := parseOn(rule) if err != nil { errs.Add(err.Subjectf("line %d", i+1)) continue diff --git a/internal/route/rules/on_test.go b/internal/route/rules/on_test.go index fb60ffc..9b4d3ee 100644 --- a/internal/route/rules/on_test.go +++ b/internal/route/rules/on_test.go @@ -12,6 +12,61 @@ import ( "golang.org/x/crypto/bcrypt" ) +func TestSplitAnd(t *testing.T) { + tests := []struct { + name string + input string + want []string + }{ + { + name: "empty", + input: "", + want: []string{}, + }, + { + name: "single", + input: "rule", + want: []string{"rule"}, + }, + { + name: "multiple", + input: "rule1 & rule2", + want: []string{"rule1", "rule2"}, + }, + { + name: "multiple_newline", + input: "rule1\n\nrule2", + want: []string{"rule1", "rule2"}, + }, + { + name: "multiple_newline_and", + input: "rule1\nrule2 & rule3", + want: []string{"rule1", "rule2", "rule3"}, + }, + { + name: "empty segment", + input: "rule1\n& &rule2& rule3", + want: []string{"rule1", "rule2", "rule3"}, + }, + { + name: "double_and", + input: "rule1\nrule2 && rule3", + want: []string{"rule1", "rule2", "rule3"}, + }, + { + name: "spaces_around", + input: " rule1\nrule2 & rule3 ", + want: []string{"rule1", "rule2", "rule3"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := splitAnd(tt.input) + ExpectEqual(t, got, tt.want) + }) + } +} + func TestParseOn(t *testing.T) { tests := []struct { name string From 7b0ed09772114ac58e29367ba7a36915a561819d Mon Sep 17 00:00:00 2001 From: yusing Date: Mon, 5 May 2025 19:32:55 +0800 Subject: [PATCH 08/13] fix(error): self referencing --- internal/gperr/utils.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/gperr/utils.go b/internal/gperr/utils.go index 1fb3e65..6dd1bb7 100644 --- a/internal/gperr/utils.go +++ b/internal/gperr/utils.go @@ -27,17 +27,16 @@ func Wrap(err error, message ...string) Error { if len(message) == 0 || message[0] == "" { return wrap(err) } - wrapped := &wrappedError{err, message[0]} //nolint:errorlint switch err := err.(type) { case *baseError: - err.Err = wrapped + err.Err = &wrappedError{err.Err, message[0]} return err case *nestedError: - err.Err = wrapped + err.Err = &wrappedError{err.Err, message[0]} return err } - return &baseError{wrapped} + return &baseError{&wrappedError{err, message[0]}} } func Unwrap(err error) Error { From f190483b4ee0b9087f88c3da522928512ee5a7e0 Mon Sep 17 00:00:00 2001 From: yusing Date: Mon, 5 May 2025 19:34:24 +0800 Subject: [PATCH 09/13] feat(rules.on): support route directive --- internal/route/rules/on.go | 17 ++++++++++++++++ internal/route/rules/on_test.go | 35 ++++++++++++++++++++++++++++++++ internal/route/rules/validate.go | 7 +++++++ 3 files changed, 59 insertions(+) diff --git a/internal/route/rules/on.go b/internal/route/rules/on.go index 61cc752..099f08b 100644 --- a/internal/route/rules/on.go +++ b/internal/route/rules/on.go @@ -8,6 +8,7 @@ import ( "github.com/gobwas/glob" "github.com/yusing/go-proxy/internal/gperr" + "github.com/yusing/go-proxy/internal/net/gphttp/httpheaders" "github.com/yusing/go-proxy/internal/net/types" "github.com/yusing/go-proxy/internal/utils/strutils" ) @@ -31,6 +32,7 @@ const ( OnPath = "path" OnRemote = "remote" OnBasicAuth = "basic_auth" + OnRoute = "route" ) var checkers = map[string]struct { @@ -229,6 +231,21 @@ var checkers = map[string]struct { } }, }, + OnRoute: { + help: Help{ + command: OnRoute, + args: map[string]string{ + "route": "the route name", + }, + }, + validate: validateSingleArg, + builder: func(args any) CheckFunc { + route := args.(string) + return func(_ Cache, r *http.Request) bool { + return r.Header.Get(httpheaders.HeaderUpstreamName) == route + } + }, + }, } var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1} diff --git a/internal/route/rules/on_test.go b/internal/route/rules/on_test.go index 9b4d3ee..1a1ed1c 100644 --- a/internal/route/rules/on_test.go +++ b/internal/route/rules/on_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/yusing/go-proxy/internal/gperr" + "github.com/yusing/go-proxy/internal/net/gphttp/httpheaders" . "github.com/yusing/go-proxy/internal/utils/testing" "golang.org/x/crypto/bcrypt" ) @@ -168,6 +169,22 @@ func TestParseOn(t *testing.T) { input: "unknown", wantErr: ErrInvalidOnTarget, }, + // route + { + name: "route_valid", + input: "route example", + wantErr: nil, + }, + { + name: "route_missing_arg", + input: "route", + wantErr: ErrExpectOneArg, + }, + { + name: "route_extra_arg", + input: "route example1 example2", + wantErr: ErrExpectOneArg, + }, } for _, tt := range tests { @@ -285,6 +302,24 @@ func TestOnCorrectness(t *testing.T) { }, want: false, }, + { + name: "route_match", + checker: "route example", + input: &http.Request{ + Header: http.Header{ + httpheaders.HeaderUpstreamName: {"example"}, + }, + }, + want: true, + }, + { + name: "route_no_match", + checker: "route example", + input: &http.Request{ + Header: http.Header{}, + }, + want: false, + }, } tests = append(tests, genCorrectnessTestCases("header", func(k, v string) *http.Request { diff --git a/internal/route/rules/validate.go b/internal/route/rules/validate.go index 7f45f10..266e01d 100644 --- a/internal/route/rules/validate.go +++ b/internal/route/rules/validate.go @@ -30,6 +30,13 @@ func (t *Tuple[T1, T2]) String() string { return fmt.Sprintf("%v:%v", t.First, t.Second) } +func validateSingleArg(args []string) (any, gperr.Error) { + if len(args) != 1 { + return nil, ErrExpectOneArg + } + return args[0], nil +} + // toStrTuple returns *StrTuple. func toStrTuple(args []string) (any, gperr.Error) { if len(args) != 2 { From afd35c183dc470027ecce4867ac04ca628904c24 Mon Sep 17 00:00:00 2001 From: yusing Date: Mon, 5 May 2025 20:05:47 +0800 Subject: [PATCH 10/13] test: fix failed tests after code changes --- internal/gperr/base.go | 4 ++-- internal/gperr/utils_test.go | 4 ++-- internal/homepage/list_icons_test.go | 18 ++++++++---------- internal/route/provider/all_fields.yaml | 2 +- internal/route/provider/docker_labels.yaml | 4 ++-- internal/utils/testing/testing.go | 2 +- 6 files changed, 16 insertions(+), 18 deletions(-) diff --git a/internal/gperr/base.go b/internal/gperr/base.go index 82616c1..99c5077 100644 --- a/internal/gperr/base.go +++ b/internal/gperr/base.go @@ -37,11 +37,11 @@ func (err *baseError) Subjectf(format string, args ...any) Error { } func (err baseError) With(extra error) Error { - return &nestedError{err.Err, []error{extra}} + return &nestedError{&err, []error{extra}} } func (err baseError) Withf(format string, args ...any) Error { - return &nestedError{err.Err, []error{fmt.Errorf(format, args...)}} + return &nestedError{&err, []error{fmt.Errorf(format, args...)}} } func (err *baseError) Error() string { diff --git a/internal/gperr/utils_test.go b/internal/gperr/utils_test.go index d9393ac..b7bab9c 100644 --- a/internal/gperr/utils_test.go +++ b/internal/gperr/utils_test.go @@ -45,11 +45,11 @@ func TestFormatting(t *testing.T) { func TestMultiError(t *testing.T) { err := testMultiErr{[]error{testErr{}, testErr{}}} plain := Plain(err) - if string(plain) != "test error\ntest error" { + if string(plain) != "test error\ntest error\n" { t.Errorf("expected test error, got %s", string(plain)) } md := Markdown(err) - if string(md) != "**test error**\n**test error**" { + if string(md) != "**test error**\n**test error**\n" { t.Errorf("expected test error, got %s", string(md)) } } diff --git a/internal/homepage/list_icons_test.go b/internal/homepage/list_icons_test.go index 1428b34..296c635 100644 --- a/internal/homepage/list_icons_test.go +++ b/internal/homepage/list_icons_test.go @@ -102,21 +102,19 @@ func TestListWalkxCodeIcons(t *testing.T) { } test := []testCases{ { - Key: NewIconKey(IconSourceWalkXCode, "2fauth"), + Key: NewIconKey(IconSourceWalkXCode, "app1"), IconMeta: IconMeta{ - SVG: true, - PNG: true, - WebP: true, - Light: true, - DisplayName: "2FAuth", + SVG: true, + PNG: true, + WebP: true, + Light: true, }, }, { - Key: NewIconKey(IconSourceWalkXCode, "dittofeed"), + Key: NewIconKey(IconSourceWalkXCode, "app2"), IconMeta: IconMeta{ - PNG: true, - WebP: true, - DisplayName: "Dittofeed", + PNG: true, + WebP: true, }, }, } diff --git a/internal/route/provider/all_fields.yaml b/internal/route/provider/all_fields.yaml index eff965a..4931b7d 100644 --- a/internal/route/provider/all_fields.yaml +++ b/internal/route/provider/all_fields.yaml @@ -26,7 +26,7 @@ example: # matching `example.y.z` hideXForwarded: homepage: name: Example App - icon: png/adguard-home.png + icon: "@selfhst/adguard-home.png" description: An example app category: example access_log: diff --git a/internal/route/provider/docker_labels.yaml b/internal/route/provider/docker_labels.yaml index cb07af7..92cbe32 100644 --- a/internal/route/provider/docker_labels.yaml +++ b/internal/route/provider/docker_labels.yaml @@ -27,7 +27,7 @@ proxy.app: | hideXForwarded: homepage: name: Example App - icon: png/adguard-home.png + icon: "@selfhst/adguard-home.png" description: An example app category: example access_log: @@ -89,7 +89,7 @@ proxy.app1.middlewares.cidr_whitelist: | message: IP not allowed proxy.app1.middlewares.hideXForwarded: proxy.app1.homepage.name: Example App -proxy.app1.homepage.icon: png/adguard-home.png +proxy.app1.homepage.icon: "@selfhst/adguard-home.png" proxy.app1.homepage.description: An example app proxy.app1.homepage.category: example proxy.app1.access_log.buffer_size: 100 diff --git a/internal/utils/testing/testing.go b/internal/utils/testing/testing.go index 41bda6d..52f118c 100644 --- a/internal/utils/testing/testing.go +++ b/internal/utils/testing/testing.go @@ -37,7 +37,7 @@ func ExpectErrorT[T error](t *testing.T, err error) { func ExpectEqual[T any](t *testing.T, got T, want T) { t.Helper() - require.EqualValues(t, got, want) + require.EqualValues(t, want, got) } func ExpectContains[T any](t *testing.T, got T, wants []T) { From e41c6530aba78fc1ac38419991332527f4636335 Mon Sep 17 00:00:00 2001 From: yusing Date: Mon, 5 May 2025 20:39:05 +0800 Subject: [PATCH 11/13] chore: update dependencies and Makefile --- Makefile | 2 +- agent/go.mod | 3 +- go.mod | 4 +- internal/dnsproviders/go.mod | 32 +++++++-------- internal/dnsproviders/go.sum | 76 +++++++++++++++--------------------- 5 files changed, 52 insertions(+), 65 deletions(-) diff --git a/Makefile b/Makefile index 52304fb..b24b79d 100755 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ docker-build-test: docker build --build-arg=MAKE_ARGS=agent=1 -t godoxy-agent . get: - for dir in ${PWD} ${PWD}/agent; do cd $$dir && go get -u ./... && go mod tidy; done + for dir in ${PWD} ${PWD}/agent ${PWD}/internal/dnsproviders; do cd $$dir && go get -u ./... && go mod tidy; done build: mkdir -p $(shell dirname ${BIN_PATH}) diff --git a/agent/go.mod b/agent/go.mod index e599bec..f006867 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -9,7 +9,7 @@ require ( github.com/docker/docker v28.1.1+incompatible github.com/rs/zerolog v1.34.0 github.com/stretchr/testify v1.10.0 - github.com/yusing/go-proxy v0.12.0 + github.com/yusing/go-proxy v0.12.3 ) replace github.com/docker/docker => github.com/godoxy-app/docker v0.0.0-20250425105916-b2ad800de7a1 @@ -38,7 +38,6 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-yaml v1.17.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect diff --git a/go.mod b/go.mod index 818e87e..adcb55b 100644 --- a/go.mod +++ b/go.mod @@ -41,8 +41,8 @@ require ( github.com/samber/slog-zerolog/v2 v2.7.3 github.com/spf13/afero v1.14.0 github.com/stretchr/testify v1.10.0 - github.com/yusing/go-proxy/agent v0.0.0-20250503173201-5f780f490224 - github.com/yusing/go-proxy/internal/dnsproviders v0.0.0-20250503173201-5f780f490224 + github.com/yusing/go-proxy/agent v0.0.0-20250504164529-2cec88d3ce0d + github.com/yusing/go-proxy/internal/dnsproviders v0.0.0-20250504164529-2cec88d3ce0d go.uber.org/atomic v1.11.0 ) diff --git a/internal/dnsproviders/go.mod b/internal/dnsproviders/go.mod index 3503653..2ff11d2 100644 --- a/internal/dnsproviders/go.mod +++ b/internal/dnsproviders/go.mod @@ -6,7 +6,7 @@ replace github.com/yusing/go-proxy => ../.. require ( github.com/go-acme/lego/v4 v4.23.1 - github.com/yusing/go-proxy v0.0.0-00010101000000-000000000000 + github.com/yusing/go-proxy v0.12.3 ) require ( @@ -23,7 +23,7 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect - github.com/aliyun/alibaba-cloud-sdk-go v1.63.106 // indirect + github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 // indirect github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect @@ -39,15 +39,15 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect github.com/aws/smithy-go v1.22.3 // indirect - github.com/baidubce/bce-sdk-go v0.9.224 // indirect + github.com/baidubce/bce-sdk-go v0.9.225 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/boombuler/barcode v1.0.2 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/civo/civogo v0.3.98 // indirect + github.com/civo/civogo v0.4.1 // indirect github.com/cloudflare/cloudflare-go v0.115.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dnsimple/dnsimple-go v1.7.0 // indirect - github.com/exoscale/egoscale/v3 v3.1.14 // indirect + github.com/exoscale/egoscale/v3 v3.1.16 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect @@ -62,7 +62,6 @@ require ( github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/go-resty/resty/v2 v2.16.5 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.17.1 // indirect github.com/gofrs/flock v0.12.1 // indirect @@ -75,12 +74,13 @@ require ( github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gophercloud/gophercloud v1.14.1 // indirect github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect + github.com/gotify/server/v2 v2.6.3 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.146 // indirect + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.147 // indirect github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect - github.com/infobloxopen/infoblox-go-client/v2 v2.9.0 // indirect + github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect @@ -113,7 +113,7 @@ require ( github.com/nrdcg/porkbun v0.4.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect - github.com/oracle/oci-go-sdk/v65 v65.89.2 // indirect + github.com/oracle/oci-go-sdk/v65 v65.89.3 // indirect github.com/ovh/go-ovh v1.7.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect @@ -141,18 +141,18 @@ require ( github.com/sony/gobreaker v1.0.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.14.0 // indirect - github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/cast v1.8.0 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/viper v1.20.1 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1150 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1158 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1136 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/transip/gotransip/v6 v6.26.0 // indirect github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect - github.com/volcengine/volc-sdk-golang v1.0.205 // indirect + github.com/volcengine/volc-sdk-golang v1.0.206 // indirect github.com/vultr/govultr/v3 v3.19.1 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect @@ -174,19 +174,19 @@ require ( golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.11.0 // indirect golang.org/x/tools v0.32.0 // indirect - google.golang.org/api v0.230.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect + google.golang.org/api v0.231.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect google.golang.org/grpc v1.72.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/ns1/ns1-go.v2 v2.14.2 // indirect + gopkg.in/ns1/ns1-go.v2 v2.14.3 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.33.0 // indirect k8s.io/apimachinery v0.33.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect + k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect diff --git a/internal/dnsproviders/go.sum b/internal/dnsproviders/go.sum index 4ed4417..d630924 100644 --- a/internal/dnsproviders/go.sum +++ b/internal/dnsproviders/go.sum @@ -654,8 +654,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.106 h1:+YPfQheppCKOPJxhWDmStF1UMJrxnA1iiwBH12t6Fa4= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.106/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 h1:qagvUyrgOnBIlVRQWOyCZGVKUIYbMBdGdJ104vBpRFU= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.107/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= @@ -702,8 +702,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjK github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k= github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= -github.com/baidubce/bce-sdk-go v0.9.224 h1:z2L8alGw/y3IUHjrLRyrxrgCvMssYTjgCd7OQdb4gt0= -github.com/baidubce/bce-sdk-go v0.9.224/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/baidubce/bce-sdk-go v0.9.225 h1:4zz/cGgrEpAIOM6pkEU3UnlNgEcpO4SV2oVpa0gAZKI= +github.com/baidubce/bce-sdk-go v0.9.225/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -717,10 +717,6 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBW github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4= github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= -github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= -github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= -github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= @@ -743,14 +739,12 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= -github.com/civo/civogo v0.3.98 h1:FEbB5oxCcHeHUK3fJODxVoMQzhpLV9Jtb7bezANTY5c= -github.com/civo/civogo v0.3.98/go.mod h1:LaEbkszc+9nXSh4YNG0sYXFGYqdQFmXXzQg0gESs2hc= +github.com/civo/civogo v0.4.1 h1:C+lwZ7hBqKy6eKy6qgviuselF0V5Z/um0x7X/eLEQ64= +github.com/civo/civogo v0.4.1/go.mod h1:LaEbkszc+9nXSh4YNG0sYXFGYqdQFmXXzQg0gESs2hc= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.115.0 h1:84/dxeeXweCc0PN5Cto44iTA8AkG1fyT11yPO5ZB7sM= github.com/cloudflare/cloudflare-go v0.115.0/go.mod h1:Ds6urDwn/TF2uIU24mu7H91xkKP8gSAHxQ44DSZgVmU= -github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= -github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -807,8 +801,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= -github.com/exoscale/egoscale/v3 v3.1.14 h1:ux1wOtx4561ZJM1sF2AFEjEY6HRj/RbtglKvZxh2iqg= -github.com/exoscale/egoscale/v3 v3.1.14/go.mod h1:t9+MpSEam94na48O/xgvvPFpQPRiwZ3kBN4/UuQtKco= +github.com/exoscale/egoscale/v3 v3.1.16 h1:JaAjY9uHLw9K5jA6kVenbTkJxgds3IU2RkrXXWV+d9s= +github.com/exoscale/egoscale/v3 v3.1.16/go.mod h1:t9+MpSEam94na48O/xgvvPFpQPRiwZ3kBN4/UuQtKco= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -893,8 +887,6 @@ github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= @@ -1000,8 +992,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 h1:gD0vax+4I+mAj+jEChEf25Ia07Jq7kYOFO5PPhAxFl4= -github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4= +github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= @@ -1047,6 +1039,8 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gotify/server/v2 v2.6.3 h1:2sLDRsQ/No1+hcFwFDvjNtwKepfCSIR8L3BkXl/Vz1I= +github.com/gotify/server/v2 v2.6.3/go.mod h1:IyeQ/iL3vetcuqUAzkCMVObIMGGJx4zb13/mVatIwE8= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -1106,8 +1100,8 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.146 h1:ld5s5UeA9zgyFsZskVD2Tr6k6VnJWkvaLm5nqvfOEf4= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.146/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.147 h1:ip9+1n9+THhYgChlQpgDLVDVTv4LVJ7AoyPBJBaX2MY= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.147/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -1117,8 +1111,8 @@ github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhK github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/infobloxopen/infoblox-go-client/v2 v2.9.0 h1:wS8kTlQVeVbrepeY83s9X+XdSa6Qah5KO+tdW+zRQXU= -github.com/infobloxopen/infoblox-go-client/v2 v2.9.0/go.mod h1:NeNJpz09efw/edzqkVivGv1bWqBXTomqYBRFbP+XBqg= +github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 h1:AKsihjFT/t6Y0keEv3p59DACcOuh0inWXdUB0ZOzYH0= +github.com/infobloxopen/infoblox-go-client/v2 v2.10.0/go.mod h1:NeNJpz09efw/edzqkVivGv1bWqBXTomqYBRFbP+XBqg= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= @@ -1163,8 +1157,6 @@ github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -1330,8 +1322,8 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= -github.com/oracle/oci-go-sdk/v65 v65.89.2 h1:w0GwID9NlT+eg3InbAwkWsazDtxVLYQ8rJb4E33Yb14= -github.com/oracle/oci-go-sdk/v65 v65.89.2/go.mod h1:u6XRPsw9tPziBh76K7GrrRXPa8P8W3BQeqJ6ZZt9VLA= +github.com/oracle/oci-go-sdk/v65 v65.89.3 h1:KSUykb5Ou54jF4SeJNjBwcDg+umbAwcvT+xhrvNDog0= +github.com/oracle/oci-go-sdk/v65 v65.89.3/go.mod h1:u6XRPsw9tPziBh76K7GrrRXPa8P8W3BQeqJ6ZZt9VLA= github.com/ovh/go-ovh v1.7.0 h1:V14nF7FwDjQrZt9g7jzcvAAQ3HN6DNShRFRMC3jLoPw= github.com/ovh/go-ovh v1.7.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -1484,8 +1476,8 @@ github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk= +github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= @@ -1527,8 +1519,8 @@ github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNG github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1136/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1150 h1:r/cHvpMZ0oO5/HOuSsPdq3Dj1YX4pF0mhZS7G5gWKEs= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1150/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1158 h1:N+C8Tz6JKGwnDFDfd3g5CkTsiKTa6/Uia0uAL0OhimE= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1158/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1136 h1:kMIdSU5IvpOROh27ToVQ3hlm6ym3lCRs9tnGCOBoZqk= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1136/go.mod h1:FpyIz3mymKaExVs6Fz27kxDBS42jqZn7vbACtxdeEH4= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= @@ -1537,8 +1529,6 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550= github.com/transip/gotransip/v6 v6.26.0/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= @@ -1548,8 +1538,8 @@ github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.205 h1:0ukVBX82JaF9N5H/D8j8jPjgJW52bQpAXabrg4WLJ88= -github.com/volcengine/volc-sdk-golang v1.0.205/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ= +github.com/volcengine/volc-sdk-golang v1.0.206 h1:7NG8FCpvu9wbx+Z4I/p3tcTS2zdBqTZtJXgydunGy6g= +github.com/volcengine/volc-sdk-golang v1.0.206/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ= github.com/vultr/govultr/v3 v3.19.1 h1:31rOP5Tz40AOc8h6Ws4ryzqAniUBffgRhy9uMG/EFvs= github.com/vultr/govultr/v3 v3.19.1/go.mod h1:q34Wd76upKmf+vxFMgaNMH3A8BbsPBmSYZUGC8oZa5w= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -1635,8 +1625,6 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= -golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -2167,8 +2155,8 @@ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60c google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= -google.golang.org/api v0.230.0 h1:2u1hni3E+UXAXrONrrkfWpi/V6cyKVAbfGVeGtC3OxM= -google.golang.org/api v0.230.0/go.mod h1:aqvtoMk7YkiXx+6U12arQFExiRV9D/ekvMCwCd/TksQ= +google.golang.org/api v0.231.0 h1:LbUD5FUl0C4qwia2bjXhCMH65yz1MLPzA/0OYEsYY7Q= +google.golang.org/api v0.231.0/go.mod h1:H52180fPI/QQlUc0F4xWfGZILdv09GCWKt2bcsn164A= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2312,8 +2300,8 @@ google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaL google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f h1:tjZsroqekhC63+WMqzmWyW5Twj/ZfR5HAlpd5YQ1Vs0= google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:Cd8IzgPo5Akum2c9R6FsXNaZbH3Jpa2gpHlW89FqlyQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f h1:N/PrbTw4kdkqNRzVfWPrBekzLuarFREcbFOiOLkXon4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -2398,8 +2386,8 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/ns1/ns1-go.v2 v2.14.2 h1:wz/toj9U20wBrmYxW4vTz7sZWED+JJVRjUBBJ7CKrzI= -gopkg.in/ns1/ns1-go.v2 v2.14.2/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= +gopkg.in/ns1/ns1-go.v2 v2.14.3 h1:Yn72GgB6AA9I4602AsLMtbC1ZKT5EUrKiG+IPS+Ovr0= +gopkg.in/ns1/ns1-go.v2 v2.14.3/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -2433,8 +2421,8 @@ k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= -k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 h1:jgJW5IePPXLGB8e/1wvd0Ich9QE97RvvF3a8J3fP/Lg= +k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= From 9eb674029eb50d95126dd54fb1ee2f2f29a0619b Mon Sep 17 00:00:00 2001 From: yusing Date: Mon, 5 May 2025 20:59:43 +0800 Subject: [PATCH 12/13] tweak(logging): rename write count variable and adjust buffer check interval --- internal/logging/accesslog/access_logger.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/logging/accesslog/access_logger.go b/internal/logging/accesslog/access_logger.go index 93ea54b..77b28d2 100644 --- a/internal/logging/accesslog/access_logger.go +++ b/internal/logging/accesslog/access_logger.go @@ -30,9 +30,8 @@ type ( writeLock sync.Mutex closed bool - wps int64 + writeCount int64 bufSize int - lastAdjust time.Time lineBufPool *synk.BytesPool // buffer pool for formatting a single log line @@ -69,7 +68,7 @@ const ( MinBufferSize = 4 * kilobyte MaxBufferSize = 8 * megabyte - bufferAdjustInterval = time.Second // How often we check & adjust + bufferAdjustInterval = 5 * time.Second // How often we check & adjust ) const defaultRotateInterval = time.Hour @@ -297,11 +296,11 @@ func (l *AccessLogger) write(data []byte) { } else if n < len(data) { l.handleErr(gperr.Errorf("%w, writing %d bytes, only %d written", io.ErrShortWrite, len(data), n)) } - atomic.AddInt64(&l.wps, int64(n)) + atomic.AddInt64(&l.writeCount, int64(n)) } func (l *AccessLogger) adjustBuffer() { - wps := int(atomic.SwapInt64(&l.wps, 0)) + wps := int(atomic.SwapInt64(&l.writeCount, 0)) / int(bufferAdjustInterval.Seconds()) origBufSize := l.bufSize newBufSize := origBufSize From c90ec8caa17e7b15631803c09f768cc44447d173 Mon Sep 17 00:00:00 2001 From: yusing Date: Tue, 6 May 2025 20:27:25 +0800 Subject: [PATCH 13/13] feat(container): add UpdatePorts method and support for host network mode --- internal/docker/container.go | 44 +++++++++++++++++++++++++------ internal/route/provider/docker.go | 8 ++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/internal/docker/container.go b/internal/docker/container.go index 874cc8d..e4ac911 100644 --- a/internal/docker/container.go +++ b/internal/docker/container.go @@ -1,6 +1,7 @@ package docker import ( + "context" "net/url" "strconv" "strings" @@ -37,10 +38,11 @@ type ( PublicHostname string `json:"public_hostname"` PrivateHostname string `json:"private_hostname"` - Aliases []string `json:"aliases"` - IsExcluded bool `json:"is_excluded"` - IsExplicit bool `json:"is_explicit"` - Running bool `json:"running"` + Aliases []string `json:"aliases"` + IsExcluded bool `json:"is_excluded"` + IsExplicit bool `json:"is_explicit"` + IsHostNetworkMode bool `json:"is_host_network_mode"` + Running bool `json:"running"` } ContainerImage struct { Author string `json:"author,omitempty"` @@ -76,10 +78,11 @@ func FromDocker(c *container.SummaryTrimmed, dockerHost string) (res *Container) PublicPortMapping: helper.getPublicPortMapping(), PrivatePortMapping: helper.getPrivatePortMapping(), - Aliases: helper.getAliases(), - IsExcluded: isExcluded, - IsExplicit: isExplicit, - Running: c.Status == "running" || c.State == "running", + Aliases: helper.getAliases(), + IsExcluded: isExcluded, + IsExplicit: isExplicit, + IsHostNetworkMode: c.HostConfig.NetworkMode == "host", + Running: c.Status == "running" || c.State == "running", } if agent.IsDockerHostAgent(dockerHost) { @@ -201,3 +204,28 @@ func (c *Container) loadDeleteIdlewatcherLabels(helper containerHelper) { } } } + +func (c *Container) UpdatePorts() error { + client, err := NewClient(c.DockerHost) + if err != nil { + return err + } + defer client.Close() + + inspect, err := client.ContainerInspect(context.Background(), c.ContainerID) + if err != nil { + return err + } + + for port := range inspect.Config.ExposedPorts { + if port.Int() == 0 { + continue + } + c.PublicPortMapping[port.Int()] = container.Port{ + PublicPort: uint16(port.Int()), + PrivatePort: uint16(port.Int()), + Type: port.Proto(), + } + } + return nil +} diff --git a/internal/route/provider/docker.go b/internal/route/provider/docker.go index 29d8518..36c1811 100755 --- a/internal/route/provider/docker.go +++ b/internal/route/provider/docker.go @@ -75,6 +75,14 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) { continue } + if container.IsHostNetworkMode { + err := container.UpdatePorts() + if err != nil { + errs.Add(gperr.PrependSubject(container.ContainerName, err)) + continue + } + } + newEntries, err := p.routesFromContainerLabels(container) if err != nil { errs.Add(err.Subject(container.ContainerName))