chore(updater): move updater packages to pkg/updaters/<name>

This commit is contained in:
Quentin McGaw
2026-04-23 03:47:57 +00:00
parent 5b01324d5f
commit 13503b0ae0
268 changed files with 1602 additions and 1026 deletions
+6 -5
View File
@@ -5,7 +5,8 @@ import (
"strings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/pkg/updaters/constants"
"github.com/qdm12/gluetun/pkg/updaters/models"
)
func boolToMarkdown(b bool) string {
@@ -42,7 +43,7 @@ const (
vpnHeader = "VPN"
)
func (s *Server) ToMarkdown(headers ...string) (markdown string) {
func serverToMarkdown(s models.Server, headers ...string) (markdown string) {
if len(headers) == 0 {
return ""
}
@@ -81,7 +82,7 @@ func (s *Server) ToMarkdown(headers ...string) (markdown string) {
case tcpHeader:
fields[i] = boolToMarkdown(s.TCP)
case udpHeader:
fields[i] = boolToMarkdown(s.UDP || s.VPN == vpn.Wireguard)
fields[i] = boolToMarkdown(s.UDP || s.VPN == constants.Wireguard)
case vpnHeader:
fields[i] = s.VPN
}
@@ -90,7 +91,7 @@ func (s *Server) ToMarkdown(headers ...string) (markdown string) {
return "| " + strings.Join(fields, " | ") + " |"
}
func (s *Servers) toMarkdown(vpnProvider string) (formatted string, err error) {
func serversToMarkdown(s models.Servers, vpnProvider string) (formatted string, err error) {
headers, err := getMarkdownHeaders(vpnProvider)
if err != nil {
return "", fmt.Errorf("getting markdown headers: %w", err)
@@ -100,7 +101,7 @@ func (s *Servers) toMarkdown(vpnProvider string) (formatted string, err error) {
entries := make([]string, len(s.Servers))
for i, server := range s.Servers {
entries[i] = server.ToMarkdown(headers...)
entries[i] = serverToMarkdown(server, headers...)
}
formatted = legend + "\n" +
+11 -10
View File
@@ -4,16 +4,17 @@ import (
"testing"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/pkg/updaters/constants"
"github.com/qdm12/gluetun/pkg/updaters/models"
"github.com/stretchr/testify/assert"
)
func Test_Servers_ToMarkdown(t *testing.T) {
func Test_serversToMarkdown(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
provider string
servers Servers
servers models.Servers
formatted string
errMessage string
}{
@@ -23,8 +24,8 @@ func Test_Servers_ToMarkdown(t *testing.T) {
},
providers.Cyberghost: {
provider: providers.Cyberghost,
servers: Servers{
Servers: []Server{
servers: models.Servers{
Servers: []models.Server{
{Country: "a", UDP: true, Hostname: "xa"},
{Country: "b", TCP: true, Hostname: "xb"},
},
@@ -36,10 +37,10 @@ func Test_Servers_ToMarkdown(t *testing.T) {
},
providers.Fastestvpn: {
provider: providers.Fastestvpn,
servers: Servers{
Servers: []Server{
{Country: "a", Hostname: "xa", VPN: vpn.OpenVPN, TCP: true},
{Country: "b", Hostname: "xb", VPN: vpn.OpenVPN, UDP: true},
servers: models.Servers{
Servers: []models.Server{
{Country: "a", Hostname: "xa", VPN: constants.OpenVPN, TCP: true},
{Country: "b", Hostname: "xb", VPN: constants.OpenVPN, UDP: true},
},
},
formatted: "| Country | Hostname | VPN | TCP | UDP |\n" +
@@ -53,7 +54,7 @@ func Test_Servers_ToMarkdown(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()
markdown, err := testCase.servers.toMarkdown(testCase.provider)
markdown, err := serversToMarkdown(testCase.servers, testCase.provider)
assert.Equal(t, testCase.formatted, markdown)
if testCase.errMessage != "" {
-32
View File
@@ -1,32 +0,0 @@
package models
import (
"net/netip"
)
type PublicIP struct {
IP netip.Addr `json:"public_ip"`
Region string `json:"region,omitempty"`
Country string `json:"country,omitempty"`
City string `json:"city,omitempty"`
Hostname string `json:"hostname,omitempty"`
Location string `json:"location,omitempty"`
Organization string `json:"organization,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
Timezone string `json:"timezone,omitempty"`
}
func (p *PublicIP) Copy() (publicIPCopy PublicIP) {
publicIPCopy = PublicIP{
IP: p.IP,
Region: p.Region,
Country: p.Country,
City: p.City,
Hostname: p.Hostname,
Location: p.Location,
Organization: p.Organization,
PostalCode: p.PostalCode,
Timezone: p.Timezone,
}
return publicIPCopy
}
-93
View File
@@ -1,93 +0,0 @@
package models
import (
"errors"
"fmt"
"net/netip"
"reflect"
"strings"
"github.com/qdm12/gluetun/internal/constants/vpn"
)
type Server struct {
VPN string `json:"vpn,omitempty"`
// Surfshark: country is also used for multi-hop
Country string `json:"country,omitempty"`
Region string `json:"region,omitempty"`
City string `json:"city,omitempty"`
ISP string `json:"isp,omitempty"`
Categories []string `json:"categories,omitempty"`
Owned bool `json:"owned,omitempty"`
Number uint16 `json:"number,omitempty"`
ServerName string `json:"server_name,omitempty"`
Hostname string `json:"hostname,omitempty"`
TCP bool `json:"tcp,omitempty"`
UDP bool `json:"udp,omitempty"`
OvpnX509 string `json:"x509,omitempty"`
RetroLoc string `json:"retroloc,omitempty"` // TODO remove in v4
MultiHop bool `json:"multihop,omitempty"`
WgPubKey string `json:"wgpubkey,omitempty"`
Free bool `json:"free,omitempty"` // TODO v4 create a SubscriptionTier struct
Premium bool `json:"premium,omitempty"`
Stream bool `json:"stream,omitempty"` // TODO v4 create a Features struct
SecureCore bool `json:"secure_core,omitempty"`
Tor bool `json:"tor,omitempty"`
PortForward bool `json:"port_forward,omitempty"`
Keep bool `json:"keep,omitempty"`
IPs []netip.Addr `json:"ips,omitempty"`
}
func (s *Server) HasMinimumInformation() (err error) {
switch {
case s.VPN == "":
return errors.New("vpn field is empty")
case len(s.IPs) == 0:
return errors.New("ips field is empty")
case s.VPN == vpn.Wireguard && (s.TCP || s.UDP):
return errors.New("no network protocol should be set")
case s.VPN == vpn.OpenVPN && !s.TCP && !s.UDP:
return errors.New("both TCP and UDP fields are false for OpenVPN")
case s.VPN == vpn.Wireguard && s.WgPubKey == "":
return errors.New("wireguard public key field is empty")
default:
return nil
}
}
func (s *Server) Equal(other Server) (equal bool) {
if !ipsAreEqual(s.IPs, other.IPs) {
return false
}
serverCopy := *s
serverCopy.IPs = nil
other.IPs = nil
return reflect.DeepEqual(serverCopy, other)
}
func ipsAreEqual(a, b []netip.Addr) (equal bool) {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i].Compare(b[i]) != 0 {
return false
}
}
return true
}
func (s *Server) Key() (key string) {
var protocols []string
if s.TCP {
protocols = append(protocols, "tcp")
}
if s.UDP {
protocols = append(protocols, "udp")
}
return fmt.Sprintf("%s-%s-%s", s.VPN, strings.Join(protocols, "-"), s.Hostname)
}
-125
View File
@@ -1,125 +0,0 @@
package models
import (
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Server_Equal(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
a *Server
b Server
equal bool
}{
"same IPs": {
a: &Server{
IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
},
b: Server{
IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
},
equal: true,
},
"same IP strings": {
a: &Server{
IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
},
b: Server{
IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
},
equal: true,
},
"different IPs": {
a: &Server{
IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4}), netip.AddrFrom4([4]byte{2, 3, 4, 5})},
},
b: Server{
IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4}), netip.AddrFrom4([4]byte{1, 2, 3, 4})},
},
},
"all fields equal": {
a: &Server{
VPN: "vpn",
Country: "country",
Region: "region",
City: "city",
ISP: "isp",
Owned: true,
Number: 1,
ServerName: "server_name",
Hostname: "hostname",
TCP: true,
UDP: true,
OvpnX509: "x509",
RetroLoc: "retroloc",
MultiHop: true,
WgPubKey: "wgpubkey",
Free: true,
Stream: true,
SecureCore: true,
Tor: true,
PortForward: true,
IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
Keep: true,
},
b: Server{
VPN: "vpn",
Country: "country",
Region: "region",
City: "city",
ISP: "isp",
Owned: true,
Number: 1,
ServerName: "server_name",
Hostname: "hostname",
TCP: true,
UDP: true,
OvpnX509: "x509",
RetroLoc: "retroloc",
MultiHop: true,
WgPubKey: "wgpubkey",
Free: true,
Stream: true,
SecureCore: true,
Tor: true,
PortForward: true,
IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
Keep: true,
},
equal: true,
},
"different field": {
a: &Server{
VPN: "vpn",
},
b: Server{
VPN: "other vpn",
},
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
ipsOfANotNil := testCase.a.IPs != nil
ipsOfBNotNil := testCase.b.IPs != nil
equal := testCase.a.Equal(testCase.b)
assert.Equal(t, testCase.equal, equal)
// Ensure IPs field is not modified
if ipsOfANotNil {
assert.NotNil(t, testCase.a)
}
if ipsOfBNotNil {
assert.NotNil(t, testCase.b)
}
})
}
}
+8 -13
View File
@@ -9,11 +9,12 @@ import (
"sort"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/pkg/updaters/models"
)
type AllServers struct {
Version uint16 // used for migration of the top level scheme
ProviderToServers map[string]Servers
ProviderToServers map[string]models.Servers
}
var _ json.Marshaler = (*AllServers)(nil)
@@ -110,7 +111,7 @@ func (a *AllServers) UnmarshalJSON(data []byte) (err error) {
return nil
}
a.ProviderToServers = make(map[string]Servers, len(keyValues))
a.ProviderToServers = make(map[string]models.Servers, len(keyValues))
allProviders := providers.All()
allProvidersSet := make(map[string]struct{}, len(allProviders))
@@ -131,7 +132,7 @@ func (a *AllServers) UnmarshalJSON(data []byte) (err error) {
key, err)
}
var servers Servers
var servers models.Servers
err = json.Unmarshal(jsonValue, &servers)
if err != nil {
return fmt.Errorf("decoding %s servers: %w",
@@ -151,24 +152,18 @@ func (a *AllServers) Count() (count int) {
return count
}
type Servers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []Server `json:"servers,omitempty"`
}
func (s *Servers) Format(vpnProvider, format string) (formatted string, err error) {
func FormatServers(s models.Servers, vpnProvider, format string) (formatted string, err error) {
switch format {
case "markdown":
return s.toMarkdown(vpnProvider)
return serversToMarkdown(s, vpnProvider)
case "json":
return s.toJSON()
return serversToJSON(s)
default:
return "", fmt.Errorf("servers format not supported: %s", format)
}
}
func (s *Servers) toJSON() (formatted string, err error) {
func serversToJSON(s models.Servers) (formatted string, err error) {
buffer := bytes.NewBuffer(nil)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
+11 -10
View File
@@ -6,6 +6,7 @@ import (
"testing"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/pkg/updaters/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -20,18 +21,18 @@ func Test_AllServers_MarshalJSON(t *testing.T) {
}{
"no provider": {
allServers: &AllServers{
ProviderToServers: map[string]Servers{},
ProviderToServers: map[string]models.Servers{},
},
dataString: `{"version":0}`,
},
"two providers": {
allServers: &AllServers{
Version: 1,
ProviderToServers: map[string]Servers{
ProviderToServers: map[string]models.Servers{
providers.Cyberghost: {
Version: 1,
Timestamp: 1000,
Servers: []Server{
Servers: []models.Server{
{Country: "A"},
{Country: "B"},
},
@@ -39,7 +40,7 @@ func Test_AllServers_MarshalJSON(t *testing.T) {
providers.Privado: {
Version: 2,
Timestamp: 2000,
Servers: []Server{
Servers: []models.Server{
{City: "C"},
{City: "D"},
},
@@ -100,11 +101,11 @@ func Test_AllServers_UnmarshalJSON(t *testing.T) {
`"privado":{"version":2,"timestamp":2000,"servers":[{"city":"C"},{"city":"D"}]}}`,
allServers: AllServers{
Version: 1,
ProviderToServers: map[string]Servers{
ProviderToServers: map[string]models.Servers{
providers.Cyberghost: {
Version: 1,
Timestamp: 1000,
Servers: []Server{
Servers: []models.Server{
{Country: "A"},
{Country: "B"},
},
@@ -112,7 +113,7 @@ func Test_AllServers_UnmarshalJSON(t *testing.T) {
providers.Privado: {
Version: 2,
Timestamp: 2000,
Servers: []Server{
Servers: []models.Server{
{City: "C"},
{City: "D"},
},
@@ -146,11 +147,11 @@ func Test_AllServers_JSON_Marshal_Unmarshal(t *testing.T) {
allServers := &AllServers{
Version: 1,
ProviderToServers: map[string]Servers{
ProviderToServers: map[string]models.Servers{
providers.Cyberghost: {
Version: 1,
Timestamp: 1000,
Servers: []Server{
Servers: []models.Server{
{Country: "A"},
{Country: "B"},
},
@@ -158,7 +159,7 @@ func Test_AllServers_JSON_Marshal_Unmarshal(t *testing.T) {
providers.Privado: {
Version: 2,
Timestamp: 2000,
Servers: []Server{
Servers: []models.Server{
{City: "C"},
{City: "D"},
},
-42
View File
@@ -1,42 +0,0 @@
package models
import "sort"
var _ sort.Interface = (*SortableServers)(nil)
type SortableServers []Server
func (s SortableServers) Len() int {
return len(s)
}
func (s SortableServers) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s SortableServers) Less(i, j int) bool {
a, b := s[i], s[j]
if a.Country == b.Country { //nolint:nestif
if a.Region == b.Region {
if a.City == b.City {
if a.ServerName == b.ServerName {
if a.Number == b.Number {
if a.Hostname == b.Hostname {
if a.ISP == b.ISP {
return a.VPN < b.VPN
}
return a.ISP < b.ISP
}
return a.Hostname < b.Hostname
}
return a.Number < b.Number
}
return a.ServerName < b.ServerName
}
return a.City < b.City
}
return a.Region < b.Region
}
return a.Country < b.Country
}