From 01179adfa838dd8069e1578c99c9f06c2c38da82 Mon Sep 17 00:00:00 2001 From: yusing Date: Sat, 26 Apr 2025 05:38:59 +0800 Subject: [PATCH] fix: loosen agent version checking - warn instead of error when version mismatch - check for major version only - better version parsing --- agent/pkg/agent/config.go | 33 +++---- agent/pkg/handler/handler.go | 4 +- internal/api/handler.go | 3 +- internal/api/v1/version.go | 12 --- internal/watcher/health/monitor/http.go | 2 +- pkg/version.go | 116 +++++++++++++++++++++++- 6 files changed, 130 insertions(+), 40 deletions(-) delete mode 100644 internal/api/v1/version.go diff --git a/agent/pkg/agent/config.go b/agent/pkg/agent/config.go index 83e506f..153fb7a 100644 --- a/agent/pkg/agent/config.go +++ b/agent/pkg/agent/config.go @@ -80,14 +80,6 @@ func (cfg *AgentConfig) Parse(addr string) error { return nil } -func withoutBuildTime(version string) string { - return strings.Split(version, "-")[0] -} - -func checkVersion(a, b string) bool { - return withoutBuildTime(a) == withoutBuildTime(b) -} - func (cfg *AgentConfig) StartWithCerts(parent task.Parent, ca, crt, key []byte) error { clientCert, err := tls.X509KeyPair(crt, key) if err != nil { @@ -113,18 +105,6 @@ func (cfg *AgentConfig) StartWithCerts(parent task.Parent, ca, crt, key []byte) ctx, cancel := context.WithTimeout(parent.Context(), 5*time.Second) defer cancel() - // check agent version - version, _, err := cfg.Fetch(ctx, EndpointVersion) - if err != nil { - return err - } - - versionStr := string(version) - // skip version check for dev versions - if strings.HasPrefix(versionStr, "v") && !checkVersion(versionStr, pkg.GetVersion()) { - return gperr.Errorf("agent version mismatch: server: %s, agent: %s", pkg.GetVersion(), versionStr) - } - // get agent name name, _, err := cfg.Fetch(ctx, EndpointName) if err != nil { @@ -132,8 +112,21 @@ func (cfg *AgentConfig) StartWithCerts(parent task.Parent, ca, crt, key []byte) } cfg.name = string(name) + cfg.l = logging.With().Str("agent", cfg.name).Logger() + // check agent version + agentVersionBytes, _, err := cfg.Fetch(ctx, EndpointVersion) + if err != nil { + return err + } + + agentVersion := string(agentVersionBytes) + + if pkg.GetVersion().IsNewerMajorThan(pkg.ParseVersion(agentVersion)) { + logging.Warn().Msgf("agent %s major version mismatch: server: %s, agent: %s", cfg.name, pkg.GetVersion(), agentVersion) + } + logging.Info().Msgf("agent %q initialized", cfg.name) return nil } diff --git a/agent/pkg/handler/handler.go b/agent/pkg/handler/handler.go index c5ce1b8..48af218 100644 --- a/agent/pkg/handler/handler.go +++ b/agent/pkg/handler/handler.go @@ -37,9 +37,7 @@ func NewAgentHandler() http.Handler { mux := ServeMux{http.NewServeMux()} mux.HandleFunc(agent.EndpointProxyHTTP+"/{path...}", ProxyHTTP) - mux.HandleMethods("GET", agent.EndpointVersion, func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(pkg.GetVersion())) - }) + mux.HandleMethods("GET", agent.EndpointVersion, pkg.GetVersionHTTPHandler()) mux.HandleMethods("GET", agent.EndpointName, func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, env.AgentName) }) diff --git a/internal/api/handler.go b/internal/api/handler.go index 6d1cbbd..6fea158 100644 --- a/internal/api/handler.go +++ b/internal/api/handler.go @@ -14,6 +14,7 @@ import ( "github.com/yusing/go-proxy/internal/metrics/uptime" "github.com/yusing/go-proxy/internal/net/gphttp/httpheaders" "github.com/yusing/go-proxy/internal/utils/strutils" + "github.com/yusing/go-proxy/pkg" ) type ( @@ -65,7 +66,7 @@ func (mux ServeMux) HandleFunc(methods, endpoint string, h any, requireAuth ...b func NewHandler(cfg config.ConfigInstance) http.Handler { mux := ServeMux{http.NewServeMux(), cfg} mux.HandleFunc("GET", "/v1", v1.Index) - mux.HandleFunc("GET", "/v1/version", v1.GetVersion) + mux.HandleFunc("GET", "/v1/version", pkg.GetVersionHTTPHandler()) mux.HandleFunc("GET", "/v1/stats", v1.Stats, true) mux.HandleFunc("POST", "/v1/reload", v1.Reload, true) diff --git a/internal/api/v1/version.go b/internal/api/v1/version.go deleted file mode 100644 index f43a9d6..0000000 --- a/internal/api/v1/version.go +++ /dev/null @@ -1,12 +0,0 @@ -package v1 - -import ( - "net/http" - - "github.com/yusing/go-proxy/internal/net/gphttp" - "github.com/yusing/go-proxy/pkg" -) - -func GetVersion(w http.ResponseWriter, r *http.Request) { - gphttp.WriteBody(w, []byte(pkg.GetVersion())) -} diff --git a/internal/watcher/health/monitor/http.go b/internal/watcher/health/monitor/http.go index ce95b23..1aa060f 100644 --- a/internal/watcher/health/monitor/http.go +++ b/internal/watcher/health/monitor/http.go @@ -53,7 +53,7 @@ func (mon *HTTPHealthMonitor) CheckHealth() (result *health.HealthCheckResult, e } req.Close = true req.Header.Set("Connection", "close") - req.Header.Set("User-Agent", "GoDoxy/"+pkg.GetVersion()) + req.Header.Set("User-Agent", "GoDoxy/"+pkg.GetVersion().String()) start := time.Now() resp, respErr := pinger.Do(req) diff --git a/pkg/version.go b/pkg/version.go index e4ef63f..af9f52d 100644 --- a/pkg/version.go +++ b/pkg/version.go @@ -1,7 +1,117 @@ package pkg -var version = "unset" +import ( + "fmt" + "net/http" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" -func GetVersion() string { - return version + "github.com/yusing/go-proxy/internal/common" + "github.com/yusing/go-proxy/internal/logging" +) + +func GetVersion() Version { + return currentVersion +} + +func GetLastVersion() Version { + return lastVersion +} + +func GetVersionHTTPHandler() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(GetVersion().String())) + } +} + +func init() { + currentVersion = ParseVersion(version) + + // ignore errors + versionFile := filepath.Join(common.DataDir, "version") + var lastVersionStr string + f, err := os.OpenFile(versionFile, os.O_RDWR|os.O_CREATE, 0o644) + if err == nil { + _, err = fmt.Fscanf(f, "%s", &lastVersionStr) + lastVersion = ParseVersion(lastVersionStr) + } + if err != nil && !os.IsNotExist(err) { + logging.Warn().Err(err).Msg("failed to read version file") + return + } + if err := f.Truncate(0); err != nil { + logging.Warn().Err(err).Msg("failed to truncate version file") + return + } + _, err = f.WriteString(version) + if err != nil { + logging.Warn().Err(err).Msg("failed to save version file") + return + } +} + +type Version struct{ Generation, Major, Minor int } + +func Ver(major, minor, patch int) Version { + return Version{major, minor, patch} +} + +func (v Version) String() string { + return fmt.Sprintf("%d.%d.%d", v.Generation, v.Major, v.Minor) +} + +func (v Version) MarshalText() ([]byte, error) { + return []byte(v.String()), nil +} + +func (v Version) IsNewerMajorThan(other Version) bool { + if v.Generation != other.Generation { + return v.Generation > other.Generation + } + return v.Major > other.Major +} + +func (v Version) IsEqual(other Version) bool { + return v.Generation == other.Generation && v.Major == other.Major && v.Minor == other.Minor +} + +var ( + version = "unset" + currentVersion Version + lastVersion Version +) + +var versionRegex = regexp.MustCompile(`^v(\d+)\.(\d+)\.(\d+)(\-\w+)?$`) + +func ParseVersion(v string) (ver Version) { + if v == "" { + return + } + + if !versionRegex.MatchString(v) { // likely feature branch (e.g. feat/some-feature) + return + } + + v = strings.Split(v, "-")[0] + v = strings.TrimPrefix(v, "v") + parts := strings.Split(v, ".") + if len(parts) != 3 { + return + } + major, err := strconv.Atoi(parts[0]) + if err != nil { + return + } + minor, err := strconv.Atoi(parts[1]) + if err != nil { + return + } + patch, err := strconv.Atoi(parts[2]) + if err != nil { + return + } + return Ver(major, minor, patch) }