mirror of
https://github.com/juanfont/headscale.git
synced 2026-07-17 16:36:02 +00:00
all: adopt slices helpers
This commit is contained in:
parent
60f0544b78
commit
ac725f27e4
18 changed files with 94 additions and 166 deletions
|
|
@ -3,6 +3,7 @@ package cli
|
|||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
|
|
@ -95,13 +96,9 @@ func initConfig() {
|
|||
var prereleases = []string{"alpha", "beta", "rc", "dev"}
|
||||
|
||||
func isPreReleaseVersion(version string) bool {
|
||||
for _, unstable := range prereleases {
|
||||
if strings.Contains(version, unstable) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return slices.ContainsFunc(prereleases, func(unstable string) bool {
|
||||
return strings.Contains(version, unstable)
|
||||
})
|
||||
}
|
||||
|
||||
// filterPreReleasesIfStable returns a function that filters out
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
|
|
@ -318,13 +319,9 @@ func printError(err error, outputFormat string) {
|
|||
}
|
||||
|
||||
func hasMachineOutputFlag() bool {
|
||||
for _, arg := range os.Args {
|
||||
if arg == outputFormatJSON || arg == outputFormatJSONLine || arg == outputFormatYAML {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return slices.ContainsFunc(os.Args, func(arg string) bool {
|
||||
return arg == outputFormatJSON || arg == outputFormatJSONLine || arg == outputFormatYAML
|
||||
})
|
||||
}
|
||||
|
||||
type tokenAuth struct {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -390,8 +391,8 @@ func (sc *StatsCollector) GetSummary() []ContainerStatsSummary {
|
|||
}
|
||||
|
||||
// Sort by container name for consistent output
|
||||
sort.Slice(summaries, func(i, j int) bool {
|
||||
return summaries[i].ContainerName < summaries[j].ContainerName
|
||||
slices.SortFunc(summaries, func(a, b ContainerStatsSummary) int {
|
||||
return cmp.Compare(a.ContainerName, b.ContainerName)
|
||||
})
|
||||
|
||||
return summaries
|
||||
|
|
@ -408,14 +409,8 @@ func calculateStatsSummary(values []float64) StatsSummary {
|
|||
sum := 0.0
|
||||
|
||||
for _, value := range values {
|
||||
if value < minVal {
|
||||
minVal = value
|
||||
}
|
||||
|
||||
if value > maxVal {
|
||||
maxVal = value
|
||||
}
|
||||
|
||||
minVal = min(minVal, value)
|
||||
maxVal = max(maxVal, value)
|
||||
sum += value
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ package capver
|
|||
//go:generate go run ../../tools/capver/main.go
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/set"
|
||||
)
|
||||
|
|
@ -25,10 +26,7 @@ func CanOldCodeBeCleanedUp() {
|
|||
}
|
||||
|
||||
func tailscaleVersSorted() []string {
|
||||
vers := xmaps.Keys(tailscaleToCapVer)
|
||||
sort.Strings(vers)
|
||||
|
||||
return vers
|
||||
return slices.Sorted(maps.Keys(tailscaleToCapVer))
|
||||
}
|
||||
|
||||
// TailscaleVersion returns the Tailscale version for the given [tailcfg.CapabilityVersion].
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
|
|
@ -68,7 +69,7 @@ func ListPeers(tx *gorm.DB, nodeID types.NodeID, peerIDs ...types.NodeID) (types
|
|||
return types.Nodes{}, err
|
||||
}
|
||||
|
||||
sort.Slice(nodes, func(i, j int) bool { return nodes[i].ID < nodes[j].ID })
|
||||
slices.SortFunc(nodes, func(a, b *types.Node) int { return cmp.Compare(a.ID, b.ID) })
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package sqliteconfig
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -90,15 +91,15 @@ const (
|
|||
JournalModeOff JournalMode = "OFF"
|
||||
)
|
||||
|
||||
// validJournalModes lists the accepted JournalMode values.
|
||||
var validJournalModes = []JournalMode{
|
||||
JournalModeWAL, JournalModeDelete, JournalModeTruncate,
|
||||
JournalModePersist, JournalModeMemory, JournalModeOff,
|
||||
}
|
||||
|
||||
// IsValid returns true if the JournalMode is valid.
|
||||
func (j JournalMode) IsValid() bool {
|
||||
switch j {
|
||||
case JournalModeWAL, JournalModeDelete, JournalModeTruncate,
|
||||
JournalModePersist, JournalModeMemory, JournalModeOff:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return slices.Contains(validJournalModes, j)
|
||||
}
|
||||
|
||||
// AutoVacuum represents SQLite auto_vacuum pragma values.
|
||||
|
|
@ -142,14 +143,14 @@ const (
|
|||
AutoVacuumIncremental AutoVacuum = "INCREMENTAL"
|
||||
)
|
||||
|
||||
// validAutoVacuums lists the accepted AutoVacuum values.
|
||||
var validAutoVacuums = []AutoVacuum{
|
||||
AutoVacuumNone, AutoVacuumFull, AutoVacuumIncremental,
|
||||
}
|
||||
|
||||
// IsValid returns true if the AutoVacuum is valid.
|
||||
func (a AutoVacuum) IsValid() bool {
|
||||
switch a {
|
||||
case AutoVacuumNone, AutoVacuumFull, AutoVacuumIncremental:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return slices.Contains(validAutoVacuums, a)
|
||||
}
|
||||
|
||||
// Synchronous represents SQLite synchronous pragma values.
|
||||
|
|
@ -201,14 +202,14 @@ const (
|
|||
SynchronousExtra Synchronous = "EXTRA"
|
||||
)
|
||||
|
||||
// validSynchronous lists the accepted Synchronous values.
|
||||
var validSynchronous = []Synchronous{
|
||||
SynchronousOff, SynchronousNormal, SynchronousFull, SynchronousExtra,
|
||||
}
|
||||
|
||||
// IsValid returns true if the Synchronous is valid.
|
||||
func (s Synchronous) IsValid() bool {
|
||||
switch s {
|
||||
case SynchronousOff, SynchronousNormal, SynchronousFull, SynchronousExtra:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return slices.Contains(validSynchronous, s)
|
||||
}
|
||||
|
||||
// TxLock represents SQLite transaction lock mode.
|
||||
|
|
@ -252,14 +253,15 @@ const (
|
|||
TxLockExclusive TxLock = "exclusive"
|
||||
)
|
||||
|
||||
// validTxLocks lists the accepted TxLock values; the empty string is valid
|
||||
// and selects the driver default.
|
||||
var validTxLocks = []TxLock{
|
||||
TxLockDeferred, TxLockImmediate, TxLockExclusive, "",
|
||||
}
|
||||
|
||||
// IsValid returns true if the TxLock is valid.
|
||||
func (t TxLock) IsValid() bool {
|
||||
switch t {
|
||||
case TxLockDeferred, TxLockImmediate, TxLockExclusive, "":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return slices.Contains(validTxLocks, t)
|
||||
}
|
||||
|
||||
// Config holds SQLite database configuration with type-safe enums.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
package hscontrol
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -262,13 +264,9 @@ func (h *Headscale) debugBatcher() string {
|
|||
}
|
||||
|
||||
// Sort by node ID
|
||||
for i := 0; i < len(nodes); i++ {
|
||||
for j := i + 1; j < len(nodes); j++ {
|
||||
if nodes[i].id > nodes[j].id {
|
||||
nodes[i], nodes[j] = nodes[j], nodes[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
slices.SortFunc(nodes, func(a, b nodeStatus) int {
|
||||
return cmp.Compare(a.id, b.id)
|
||||
})
|
||||
|
||||
// Output sorted nodes
|
||||
for _, node := range nodes {
|
||||
|
|
|
|||
|
|
@ -148,20 +148,12 @@ func (h *Headscale) handleVerifyRequest(
|
|||
return NewHTTPError(http.StatusBadRequest, "Bad Request: invalid JSON", fmt.Errorf("parsing DERP client request: %w", err))
|
||||
}
|
||||
|
||||
nodes := h.state.ListNodes()
|
||||
|
||||
// Check if any node has the requested NodeKey
|
||||
var nodeKeyFound bool
|
||||
|
||||
for _, node := range nodes.All() {
|
||||
if node.NodeKey() == derpAdmitClientRequest.NodePublic {
|
||||
nodeKeyFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
allow := h.state.ListNodes().ContainsFunc(func(n types.NodeView) bool {
|
||||
return n.NodeKey() == derpAdmitClientRequest.NodePublic
|
||||
})
|
||||
|
||||
resp := &tailcfg.DERPAdmitClientResponse{
|
||||
Allow: nodeKeyFound,
|
||||
Allow: allow,
|
||||
}
|
||||
|
||||
return json.NewEncoder(writer).Encode(resp)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package mapper
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/policy"
|
||||
|
|
@ -301,8 +301,8 @@ func (b *MapResponseBuilder) buildTailPeers(peers views.Slice[types.NodeView]) (
|
|||
}
|
||||
|
||||
// Peers is always returned sorted by Node.ID.
|
||||
sort.SliceStable(tailPeers, func(x, y int) bool {
|
||||
return tailPeers[x].ID < tailPeers[y].ID
|
||||
slices.SortStableFunc(tailPeers, func(a, b *tailcfg.Node) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
return tailPeers, nil
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package mapper
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
|
@ -142,9 +143,7 @@ func (mc *multiChannelNodeConn) stopConnection(conn *connectionEntry) {
|
|||
// Caller must hold mc.mutex.
|
||||
func (mc *multiChannelNodeConn) removeConnectionAtIndexLocked(i int, stopConnection bool) *connectionEntry {
|
||||
conn := mc.connections[i]
|
||||
copy(mc.connections[i:], mc.connections[i+1:])
|
||||
mc.connections[len(mc.connections)-1] = nil // release pointer for GC
|
||||
mc.connections = mc.connections[:len(mc.connections)-1]
|
||||
mc.connections = slices.Delete(mc.connections, i, i+1)
|
||||
|
||||
if stopConnection {
|
||||
mc.stopConnection(conn)
|
||||
|
|
@ -353,32 +352,22 @@ func (mc *multiChannelNodeConn) send(data *tailcfg.MapResponse) error {
|
|||
// Remove by pointer identity: only remove entries that still exist
|
||||
// in the current connections slice and match a failed pointer.
|
||||
// New connections added since the snapshot are not affected.
|
||||
failedSet := make(map[*connectionEntry]struct{}, len(failed))
|
||||
for _, f := range failed {
|
||||
failedSet[f] = struct{}{}
|
||||
}
|
||||
|
||||
clean := mc.connections[:0]
|
||||
for _, conn := range mc.connections {
|
||||
if _, isFailed := failedSet[conn]; !isFailed {
|
||||
clean = append(clean, conn)
|
||||
} else {
|
||||
mc.log.Debug().
|
||||
Str(zf.ConnID, conn.id).
|
||||
Msg("send: removing failed connection")
|
||||
// Tear down the owning session so the old serveLongPoll
|
||||
// goroutine exits instead of lingering as a stale session.
|
||||
mc.stopConnection(conn)
|
||||
// DeleteFunc preserves order and zeroes trailing slots so removed
|
||||
// *connectionEntry values are not retained by the backing array.
|
||||
mc.connections = slices.DeleteFunc(mc.connections, func(conn *connectionEntry) bool {
|
||||
if !slices.Contains(failed, conn) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Nil out trailing slots so removed *connectionEntry values
|
||||
// are not retained by the backing array.
|
||||
for i := len(clean); i < len(mc.connections); i++ {
|
||||
mc.connections[i] = nil
|
||||
}
|
||||
mc.log.Debug().
|
||||
Str(zf.ConnID, conn.id).
|
||||
Msg("send: removing failed connection")
|
||||
// Tear down the owning session so the old serveLongPoll
|
||||
// goroutine exits instead of lingering as a stale session.
|
||||
mc.stopConnection(conn)
|
||||
|
||||
mc.connections = clean
|
||||
return true
|
||||
})
|
||||
mc.mutex.Unlock()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import (
|
|||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/samber/lo"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
|
|
@ -66,8 +65,7 @@ func ApproveRoutesWithPolicy(pm PolicyManager, nv types.NodeView, currentApprove
|
|||
}
|
||||
|
||||
// Start with ALL currently approved routes - we never remove approved routes
|
||||
newApproved := make([]netip.Prefix, len(currentApproved))
|
||||
copy(newApproved, currentApproved)
|
||||
newApproved := slices.Clone(currentApproved)
|
||||
|
||||
// Then, check for new routes that can be auto-approved
|
||||
for _, route := range announcedRoutes {
|
||||
|
|
@ -86,13 +84,12 @@ func ApproveRoutesWithPolicy(pm PolicyManager, nv types.NodeView, currentApprove
|
|||
// Sort and deduplicate
|
||||
slices.SortFunc(newApproved, netip.Prefix.Compare)
|
||||
newApproved = slices.Compact(newApproved)
|
||||
newApproved = lo.Filter(newApproved, func(route netip.Prefix, index int) bool {
|
||||
return route.IsValid()
|
||||
newApproved = slices.DeleteFunc(newApproved, func(route netip.Prefix) bool {
|
||||
return !route.IsValid()
|
||||
})
|
||||
|
||||
// Sort the current approved for comparison
|
||||
sortedCurrent := make([]netip.Prefix, len(currentApproved))
|
||||
copy(sortedCurrent, currentApproved)
|
||||
sortedCurrent := slices.Clone(currentApproved)
|
||||
slices.SortFunc(sortedCurrent, netip.Prefix.Compare)
|
||||
|
||||
// Only update if the routes actually changed
|
||||
|
|
|
|||
|
|
@ -1455,20 +1455,9 @@ func (pm *PolicyManager) invalidateAutogroupSelfCache(oldNodes, newNodes views.S
|
|||
affectedUsers[newNode.TypedUserID()] = struct{}{}
|
||||
}
|
||||
|
||||
// Check if IPs changed (simple check - could be more sophisticated)
|
||||
oldIPs := oldNode.IPs()
|
||||
|
||||
newIPs := newNode.IPs()
|
||||
if len(oldIPs) != len(newIPs) {
|
||||
// Check if IPs changed.
|
||||
if !slices.Equal(oldNode.IPs(), newNode.IPs()) {
|
||||
affectedUsers[newNode.TypedUserID()] = struct{}{}
|
||||
} else {
|
||||
// Check if any IPs are different
|
||||
for i, oldIP := range oldIPs {
|
||||
if i >= len(newIPs) || oldIP != newIPs[i] {
|
||||
affectedUsers[newNode.TypedUserID()] = struct{}{}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package v2
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
|
|
@ -92,14 +93,7 @@ func (r SSHPolicyTestResults) Errors() string {
|
|||
}
|
||||
|
||||
func sortedUsers(m map[string][]string) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
slices.Sort(keys)
|
||||
|
||||
return keys
|
||||
return slices.Sorted(maps.Keys(m))
|
||||
}
|
||||
|
||||
// displayUser shows an empty username as `""` rather than blank.
|
||||
|
|
@ -394,10 +388,7 @@ func resolveSSHTestSource(
|
|||
return nil, 0, nil
|
||||
}
|
||||
|
||||
out := make([]netip.Addr, 0)
|
||||
for a := range addrs.Iter() {
|
||||
out = append(out, a)
|
||||
}
|
||||
out := slices.Collect(addrs.Iter())
|
||||
|
||||
var userID uint
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"slices"
|
||||
|
||||
realclientip "github.com/realclientip/realclientip-go"
|
||||
)
|
||||
|
|
@ -77,13 +78,9 @@ func peerTrusted(remoteAddr string, trusted []netip.Prefix) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
for _, p := range trusted {
|
||||
if p.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return slices.ContainsFunc(trusted, func(p netip.Prefix) bool {
|
||||
return p.Contains(addr)
|
||||
})
|
||||
}
|
||||
|
||||
func parsePeerAddr(remoteAddr string) (netip.Addr, bool) {
|
||||
|
|
|
|||
|
|
@ -213,13 +213,7 @@ func (r Change) ShouldSendToNode(nodeID types.NodeID) bool {
|
|||
|
||||
// HasFull returns true if any response in the slice is a full update ([Change.IsFull]).
|
||||
func HasFull(rs []Change) bool {
|
||||
for _, r := range rs {
|
||||
if r.IsFull() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return slices.ContainsFunc(rs, Change.IsFull)
|
||||
}
|
||||
|
||||
// SplitTargetedAndBroadcast separates responses into targeted (to specific node) and broadcast.
|
||||
|
|
|
|||
|
|
@ -243,13 +243,7 @@ func (node *Node) IPs() []netip.Addr {
|
|||
|
||||
// HasIP reports if a node has a given IP address.
|
||||
func (node *Node) HasIP(i netip.Addr) bool {
|
||||
for _, ip := range node.IPs() {
|
||||
if ip.Compare(i) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return slices.Contains(node.IPs(), i)
|
||||
}
|
||||
|
||||
// IsTagged reports if a device is tagged and therefore should not be treated
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package util
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -18,10 +19,6 @@ func YesNo(msg string) bool {
|
|||
_, _ = fmt.Scanln(&resp)
|
||||
|
||||
resp = strings.ToLower(resp)
|
||||
switch resp {
|
||||
case "y", "yes", "sure":
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
return slices.Contains([]string{"y", "yes", "sure"}, resp)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -1295,8 +1295,8 @@ func (t *HeadscaleInContainer) ListNodes(
|
|||
}
|
||||
}
|
||||
|
||||
sort.Slice(ret, func(i, j int) bool {
|
||||
return cmp.Compare(ret[i].GetId(), ret[j].GetId()) == -1
|
||||
slices.SortFunc(ret, func(a, b *v1.Node) int {
|
||||
return cmp.Compare(a.GetId(), b.GetId())
|
||||
})
|
||||
|
||||
return ret, nil
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue