mirror of
https://github.com/yusing/godoxy.git
synced 2026-02-17 16:07:44 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b3bf84148 | ||
|
|
62a667758d | ||
|
|
ddd27156fc | ||
|
|
af8e2d56b2 | ||
|
|
74a215b894 | ||
|
|
ccdc0046fd | ||
|
|
2f7fdc4c51 | ||
|
|
de1f4da126 |
15
README.md
15
README.md
@@ -33,6 +33,7 @@ Have questions? Ask [ChatGPT](https://chatgpt.com/g/g-6825390374b481919ad482f2e4
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Setup](#setup)
|
||||
- [How does GoDoxy work](#how-does-godoxy-work)
|
||||
- [Update / Uninstall system agent](#update--uninstall-system-agent)
|
||||
- [Screenshots](#screenshots)
|
||||
- [idlesleeper](#idlesleeper)
|
||||
- [Metrics and Logs](#metrics-and-logs)
|
||||
@@ -128,6 +129,20 @@ Configure Wildcard DNS Record(s) to point to machine running `GoDoxy`, e.g.
|
||||
>
|
||||
> For example, with the label `proxy.aliases: qbt` you can access your app via `qbt.domain.com`.
|
||||
|
||||
## Update / Uninstall system agent
|
||||
|
||||
Update:
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://github.com/yusing/godoxy/raw/refs/heads/main/scripts/install-agent.sh)" -- update
|
||||
```
|
||||
|
||||
Uninstall:
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://github.com/yusing/godoxy/raw/refs/heads/main/scripts/install-agent.sh)" -- uninstall
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
### idlesleeper
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
- [安裝](#安裝)
|
||||
- [手動安裝](#手動安裝)
|
||||
- [資料夾結構](#資料夾結構)
|
||||
- [更新 / 卸載系統代理 (System Agent)](#更新--卸載系統代理-system-agent)
|
||||
- [截圖](#截圖)
|
||||
- [閒置休眠](#閒置休眠)
|
||||
- [監控](#監控)
|
||||
@@ -144,6 +145,20 @@
|
||||
└── .env
|
||||
```
|
||||
|
||||
## 更新 / 卸載系統代理 (System Agent)
|
||||
|
||||
更新:
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://github.com/yusing/godoxy/raw/refs/heads/main/scripts/install-agent.sh)" -- update
|
||||
```
|
||||
|
||||
卸載:
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://github.com/yusing/godoxy/raw/refs/heads/main/scripts/install-agent.sh)" -- uninstall
|
||||
```
|
||||
|
||||
## 截圖
|
||||
|
||||
### 閒置休眠
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
type AgentConfig struct {
|
||||
Addr string `json:"addr"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Version pkg.Version `json:"version"`
|
||||
Runtime ContainerRuntime `json:"runtime"`
|
||||
|
||||
httpClient *http.Client
|
||||
@@ -148,11 +148,10 @@ func (cfg *AgentConfig) StartWithCerts(ctx context.Context, ca, crt, key []byte)
|
||||
return fmt.Errorf("failed to get agent runtime: HTTP %d %s", status, runtimeBytes)
|
||||
}
|
||||
|
||||
cfg.Version = string(agentVersionBytes)
|
||||
agentVersion := pkg.ParseVersion(cfg.Version)
|
||||
cfg.Version = pkg.ParseVersion(string(agentVersionBytes))
|
||||
|
||||
if serverVersion.IsNewerMajorThan(agentVersion) {
|
||||
log.Warn().Msgf("agent %s major version mismatch: server: %s, agent: %s", cfg.Name, serverVersion, agentVersion)
|
||||
if serverVersion.IsNewerThanMajor(cfg.Version) {
|
||||
log.Warn().Msgf("agent %s major version mismatch: server: %s, agent: %s", cfg.Name, serverVersion, cfg.Version)
|
||||
}
|
||||
|
||||
log.Info().Msgf("agent %q initialized", cfg.Name)
|
||||
|
||||
76
agent/pkg/agentproxy/config.go
Normal file
76
agent/pkg/agentproxy/config.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package agentproxy
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
route "github.com/yusing/go-proxy/internal/route/types"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Scheme string `json:"scheme,omitempty"`
|
||||
Host string `json:"host,omitempty"` // host or host:port
|
||||
|
||||
route.HTTPConfig
|
||||
}
|
||||
|
||||
func ConfigFromHeaders(h http.Header) (Config, error) {
|
||||
cfg, err := proxyConfigFromHeaders(h)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
if cfg.Host == "" {
|
||||
cfg = proxyConfigFromHeadersLegacy(h)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func proxyConfigFromHeadersLegacy(h http.Header) (cfg Config) {
|
||||
cfg.Host = h.Get(HeaderXProxyHost)
|
||||
isHTTPS, _ := strconv.ParseBool(h.Get(HeaderXProxyHTTPS))
|
||||
cfg.NoTLSVerify, _ = strconv.ParseBool(h.Get(HeaderXProxySkipTLSVerify))
|
||||
responseHeaderTimeout, err := strconv.Atoi(h.Get(HeaderXProxyResponseHeaderTimeout))
|
||||
if err != nil {
|
||||
responseHeaderTimeout = 0
|
||||
}
|
||||
cfg.ResponseHeaderTimeout = time.Duration(responseHeaderTimeout) * time.Second
|
||||
|
||||
cfg.Scheme = "http"
|
||||
if isHTTPS {
|
||||
cfg.Scheme = "https"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func proxyConfigFromHeaders(h http.Header) (cfg Config, err error) {
|
||||
cfg.Scheme = h.Get(HeaderXProxyScheme)
|
||||
cfg.Host = h.Get(HeaderXProxyHost)
|
||||
|
||||
cfgBase64 := h.Get(HeaderXProxyConfig)
|
||||
cfgJSON, err := base64.StdEncoding.DecodeString(cfgBase64)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(cfgJSON, &cfg)
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func (cfg *Config) SetAgentProxyConfigHeadersLegacy(h http.Header) {
|
||||
h.Set(HeaderXProxyHost, cfg.Host)
|
||||
h.Set(HeaderXProxyHTTPS, strconv.FormatBool(cfg.Scheme == "https"))
|
||||
h.Set(HeaderXProxySkipTLSVerify, strconv.FormatBool(cfg.NoTLSVerify))
|
||||
h.Set(HeaderXProxyResponseHeaderTimeout, strconv.Itoa(int(cfg.ResponseHeaderTimeout.Round(time.Second).Seconds())))
|
||||
}
|
||||
|
||||
func (cfg *Config) SetAgentProxyConfigHeaders(h http.Header) {
|
||||
h.Set(HeaderXProxyHost, cfg.Host)
|
||||
h.Set(HeaderXProxyScheme, string(cfg.Scheme))
|
||||
cfgJSON, _ := json.Marshal(cfg.HTTPConfig)
|
||||
cfgBase64 := base64.StdEncoding.EncodeToString(cfgJSON)
|
||||
h.Set(HeaderXProxyConfig, cfgBase64)
|
||||
}
|
||||
@@ -1,27 +1,14 @@
|
||||
package agentproxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
const (
|
||||
HeaderXProxyScheme = "X-Proxy-Scheme"
|
||||
HeaderXProxyHost = "X-Proxy-Host"
|
||||
HeaderXProxyConfig = "X-Proxy-Config"
|
||||
)
|
||||
|
||||
// deprecated
|
||||
const (
|
||||
HeaderXProxyHost = "X-Proxy-Host"
|
||||
HeaderXProxyHTTPS = "X-Proxy-Https"
|
||||
HeaderXProxySkipTLSVerify = "X-Proxy-Skip-Tls-Verify"
|
||||
HeaderXProxyResponseHeaderTimeout = "X-Proxy-Response-Header-Timeout"
|
||||
)
|
||||
|
||||
type AgentProxyHeaders struct {
|
||||
Host string
|
||||
IsHTTPS bool
|
||||
SkipTLSVerify bool
|
||||
ResponseHeaderTimeout int
|
||||
}
|
||||
|
||||
func SetAgentProxyHeaders(r *http.Request, headers *AgentProxyHeaders) {
|
||||
r.Header.Set(HeaderXProxyHost, headers.Host)
|
||||
r.Header.Set(HeaderXProxyHTTPS, strconv.FormatBool(headers.IsHTTPS))
|
||||
r.Header.Set(HeaderXProxySkipTLSVerify, strconv.FormatBool(headers.SkipTLSVerify))
|
||||
r.Header.Set(HeaderXProxyResponseHeaderTimeout, strconv.Itoa(headers.ResponseHeaderTimeout))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||
@@ -24,31 +23,24 @@ func NewTransport() *http.Transport {
|
||||
}
|
||||
|
||||
func ProxyHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
host := r.Header.Get(agentproxy.HeaderXProxyHost)
|
||||
isHTTPS, _ := strconv.ParseBool(r.Header.Get(agentproxy.HeaderXProxyHTTPS))
|
||||
skipTLSVerify, _ := strconv.ParseBool(r.Header.Get(agentproxy.HeaderXProxySkipTLSVerify))
|
||||
responseHeaderTimeout, err := strconv.Atoi(r.Header.Get(agentproxy.HeaderXProxyResponseHeaderTimeout))
|
||||
cfg, err := agentproxy.ConfigFromHeaders(r.Header)
|
||||
if err != nil {
|
||||
responseHeaderTimeout = 0
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
http.Error(w, "missing required headers", http.StatusBadRequest)
|
||||
http.Error(w, fmt.Sprintf("failed to parse agent proxy config: %s", err.Error()), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
if isHTTPS {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
transport := NewTransport()
|
||||
if skipTLSVerify {
|
||||
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
if cfg.ResponseHeaderTimeout > 0 {
|
||||
transport.ResponseHeaderTimeout = cfg.ResponseHeaderTimeout
|
||||
}
|
||||
if cfg.DisableCompression {
|
||||
transport.DisableCompression = true
|
||||
}
|
||||
|
||||
if responseHeaderTimeout > 0 {
|
||||
transport.ResponseHeaderTimeout = time.Duration(responseHeaderTimeout) * time.Second
|
||||
transport.TLSClientConfig, err = cfg.BuildTLSConfig(r.URL)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to build TLS client config: %s", err.Error()), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
r.URL.Scheme = ""
|
||||
@@ -58,8 +50,8 @@ func ProxyHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
rp := &httputil.ReverseProxy{
|
||||
Director: func(r *http.Request) {
|
||||
r.URL.Scheme = scheme
|
||||
r.URL.Host = host
|
||||
r.URL.Scheme = cfg.Scheme
|
||||
r.URL.Host = cfg.Host
|
||||
},
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
# Stage 1: deps
|
||||
FROM golang:1.25.0-alpine AS deps
|
||||
FROM alpine:3.22 AS deps
|
||||
HEALTHCHECK NONE
|
||||
|
||||
# package version does not matter
|
||||
# trunk-ignore(hadolint/DL3018)
|
||||
RUN apk add --no-cache tzdata make libcap-setcap
|
||||
RUN apk add --no-cache tzdata
|
||||
|
||||
# Stage 3: Final image
|
||||
FROM alpine:3.22
|
||||
# Stage 2: Final image
|
||||
FROM deps
|
||||
|
||||
LABEL maintainer="yusing@6uo.me"
|
||||
LABEL proxy.exclude=1
|
||||
|
||||
# copy timezone data
|
||||
COPY --from=deps /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
|
||||
# copy certs
|
||||
COPY --from=deps /etc/ssl/certs /etc/ssl/certs
|
||||
|
||||
ARG TARGET
|
||||
ENV TARGET=${TARGET}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
@@ -20,6 +19,7 @@ import (
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
||||
"github.com/yusing/go-proxy/pkg"
|
||||
)
|
||||
|
||||
type ReveseProxyRoute struct {
|
||||
@@ -44,10 +44,12 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) {
|
||||
trans = a.Transport()
|
||||
proxyURL = nettypes.NewURL(agent.HTTPProxyURL)
|
||||
} else {
|
||||
trans = gphttp.NewTransport()
|
||||
if httpConfig.NoTLSVerify {
|
||||
trans.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec
|
||||
tlsConfig, err := httpConfig.BuildTLSConfig(&base.ProxyURL.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trans = gphttp.NewTransportWithTLSConfig(tlsConfig)
|
||||
if httpConfig.ResponseHeaderTimeout > 0 {
|
||||
trans.ResponseHeaderTimeout = httpConfig.ResponseHeaderTimeout
|
||||
}
|
||||
@@ -67,15 +69,19 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) {
|
||||
}
|
||||
|
||||
if a != nil {
|
||||
headers := &agentproxy.AgentProxyHeaders{
|
||||
Host: base.ProxyURL.Host,
|
||||
IsHTTPS: base.ProxyURL.Scheme == "https",
|
||||
SkipTLSVerify: httpConfig.NoTLSVerify,
|
||||
ResponseHeaderTimeout: int(httpConfig.ResponseHeaderTimeout.Seconds()),
|
||||
cfg := agentproxy.Config{
|
||||
Scheme: base.ProxyURL.Scheme,
|
||||
Host: base.ProxyURL.Host,
|
||||
HTTPConfig: httpConfig,
|
||||
}
|
||||
setHeaderFunc := cfg.SetAgentProxyConfigHeaders
|
||||
if !a.Version.IsOlderThan(pkg.Ver(0, 18, 6)) {
|
||||
setHeaderFunc = cfg.SetAgentProxyConfigHeadersLegacy
|
||||
}
|
||||
|
||||
ori := rp.HandlerFunc
|
||||
rp.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
||||
agentproxy.SetAgentProxyHeaders(r, headers)
|
||||
setHeaderFunc(r.Header)
|
||||
ori(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,14 @@ func (s *TCPTCPStream) LocalAddr() net.Addr {
|
||||
}
|
||||
|
||||
func (s *TCPTCPStream) MarshalZerologObject(e *zerolog.Event) {
|
||||
e.Str("protocol", "tcp-tcp").Str("listen", s.listener.Addr().String()).Str("dst", s.dst.String())
|
||||
e.Str("protocol", "tcp-tcp")
|
||||
|
||||
if s.listener != nil {
|
||||
e.Str("listen", s.listener.Addr().String())
|
||||
}
|
||||
if s.dst != nil {
|
||||
e.Str("dst", s.dst.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TCPTCPStream) listen(ctx context.Context) {
|
||||
|
||||
@@ -113,7 +113,13 @@ func (s *UDPUDPStream) LocalAddr() net.Addr {
|
||||
}
|
||||
|
||||
func (s *UDPUDPStream) MarshalZerologObject(e *zerolog.Event) {
|
||||
e.Str("protocol", "udp-udp").Str("name", s.name).Str("dst", s.dst.String())
|
||||
e.Str("protocol", "udp-udp")
|
||||
if s.name != "" {
|
||||
e.Str("name", s.name)
|
||||
}
|
||||
if s.dst != nil {
|
||||
e.Str("dst", s.dst.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UDPUDPStream) listen(ctx context.Context) {
|
||||
|
||||
@@ -1,11 +1,118 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/gperr"
|
||||
)
|
||||
|
||||
type HTTPConfig struct {
|
||||
NoTLSVerify bool `json:"no_tls_verify,omitempty"`
|
||||
ResponseHeaderTimeout time.Duration `json:"response_header_timeout,omitempty" swaggertype:"primitive,integer"`
|
||||
DisableCompression bool `json:"disable_compression,omitempty"`
|
||||
|
||||
// SSL/TLS proxy options (nginx-like)
|
||||
SSLServerName *string `json:"ssl_server_name,omitempty"` // SNI server name
|
||||
SSLTrustedCertificate string `json:"ssl_trusted_certificate,omitempty"` // Path to trusted CA certificates
|
||||
SSLCertificate string `json:"ssl_certificate,omitempty"` // Path to client certificate
|
||||
SSLCertificateKey string `json:"ssl_certificate_key,omitempty"` // Path to client certificate key
|
||||
SSLProtocols []string `json:"ssl_protocols,omitempty"` // Allowed TLS protocols
|
||||
}
|
||||
|
||||
// BuildTLSConfig creates a TLS configuration based on the HTTP config options.
|
||||
func (cfg *HTTPConfig) BuildTLSConfig(targetURL *url.URL) (*tls.Config, gperr.Error) {
|
||||
tlsConfig := &tls.Config{}
|
||||
|
||||
// Handle InsecureSkipVerify (legacy NoTLSVerify option)
|
||||
if cfg.NoTLSVerify {
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
// Handle ssl_server_name (SNI)
|
||||
if cfg.SSLServerName != nil {
|
||||
switch *cfg.SSLServerName {
|
||||
case "off":
|
||||
// Disable SNI by setting empty string
|
||||
tlsConfig.ServerName = ""
|
||||
case "on", "":
|
||||
// Use hostname from target URL for SNI
|
||||
tlsConfig.ServerName = targetURL.Hostname()
|
||||
default:
|
||||
tlsConfig.ServerName = *cfg.SSLServerName
|
||||
}
|
||||
} else {
|
||||
// Default behavior - use hostname for SNI
|
||||
tlsConfig.ServerName = targetURL.Hostname()
|
||||
}
|
||||
|
||||
// Handle ssl_trusted_certificate
|
||||
if cfg.SSLTrustedCertificate != "" {
|
||||
caCertData, err := os.ReadFile(cfg.SSLTrustedCertificate)
|
||||
if err != nil {
|
||||
return nil, gperr.New("failed to read trusted certificate file").
|
||||
Subject(cfg.SSLTrustedCertificate).
|
||||
With(err)
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
if !caCertPool.AppendCertsFromPEM(caCertData) {
|
||||
return nil, gperr.New("failed to parse trusted certificates").
|
||||
Subject(cfg.SSLTrustedCertificate)
|
||||
}
|
||||
tlsConfig.RootCAs = caCertPool
|
||||
}
|
||||
|
||||
// Handle ssl_certificate and ssl_certificate_key (client certificates)
|
||||
if cfg.SSLCertificate != "" {
|
||||
if cfg.SSLCertificateKey == "" {
|
||||
return nil, gperr.New("ssl_certificate_key is required when ssl_certificate is specified")
|
||||
}
|
||||
|
||||
clientCert, err := tls.LoadX509KeyPair(cfg.SSLCertificate, cfg.SSLCertificateKey)
|
||||
if err != nil {
|
||||
return nil, gperr.New("failed to load client certificate").
|
||||
Subject(cfg.SSLCertificate).
|
||||
With(err)
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{clientCert}
|
||||
}
|
||||
|
||||
// Handle ssl_protocols (TLS versions)
|
||||
if len(cfg.SSLProtocols) > 0 {
|
||||
var minVersion, maxVersion uint16
|
||||
|
||||
for _, protocol := range cfg.SSLProtocols {
|
||||
var version uint16
|
||||
switch strings.ToLower(protocol) {
|
||||
case "tlsv1.0":
|
||||
version = tls.VersionTLS10
|
||||
case "tlsv1.1":
|
||||
version = tls.VersionTLS11
|
||||
case "tlsv1.2":
|
||||
version = tls.VersionTLS12
|
||||
case "tlsv1.3":
|
||||
version = tls.VersionTLS13
|
||||
default:
|
||||
return nil, gperr.New("unsupported TLS protocol").
|
||||
Subject(protocol)
|
||||
}
|
||||
|
||||
if minVersion == 0 || version < minVersion {
|
||||
minVersion = version
|
||||
}
|
||||
if maxVersion == 0 || version > maxVersion {
|
||||
maxVersion = version
|
||||
}
|
||||
}
|
||||
|
||||
tlsConfig.MinVersion = minVersion
|
||||
tlsConfig.MaxVersion = maxVersion
|
||||
}
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ func init() {
|
||||
|
||||
type Version struct{ Generation, Major, Minor int }
|
||||
|
||||
func Ver(major, minor, patch int) Version {
|
||||
return Version{major, minor, patch}
|
||||
func Ver(gen, major, minor int) Version {
|
||||
return Version{gen, major, minor}
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
@@ -55,13 +55,38 @@ func (v Version) MarshalText() ([]byte, error) {
|
||||
return []byte(v.String()), nil
|
||||
}
|
||||
|
||||
func (v Version) IsNewerMajorThan(other Version) bool {
|
||||
func (v Version) IsNewerThan(other Version) bool {
|
||||
if v.Generation != other.Generation {
|
||||
return v.Generation > other.Generation
|
||||
}
|
||||
if v.Major != other.Major {
|
||||
return v.Major > other.Major
|
||||
}
|
||||
return v.Minor > other.Minor
|
||||
}
|
||||
|
||||
func (v Version) IsNewerThanMajor(other Version) bool {
|
||||
if v.Generation != other.Generation {
|
||||
return v.Generation > other.Generation
|
||||
}
|
||||
return v.Major > other.Major
|
||||
}
|
||||
|
||||
func (v Version) IsOlderThan(other Version) bool {
|
||||
return !v.IsNewerThan(other)
|
||||
}
|
||||
|
||||
func (v Version) IsOlderThanMajor(other Version) bool {
|
||||
if v.Generation != other.Generation {
|
||||
return v.Generation < other.Generation
|
||||
}
|
||||
return v.Major < other.Major
|
||||
}
|
||||
|
||||
func (v Version) IsOlderMajorThan(other Version) bool {
|
||||
return !v.IsNewerThanMajor(other)
|
||||
}
|
||||
|
||||
func (v Version) IsEqual(other Version) bool {
|
||||
return v.Generation == other.Generation && v.Major == other.Major && v.Minor == other.Minor
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user