diff --git a/cmd/hi/stats.go b/cmd/hi/stats.go index b68215a6..c1bb9cfe 100644 --- a/cmd/hi/stats.go +++ b/cmd/hi/stats.go @@ -1,12 +1,13 @@ package main import ( + "cmp" "context" "encoding/json" "errors" "fmt" "log" - "sort" + "slices" "strings" "sync" "time" @@ -371,8 +372,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 diff --git a/hscontrol/db/node.go b/hscontrol/db/node.go index bf407bb4..3887350b 100644 --- a/hscontrol/db/node.go +++ b/hscontrol/db/node.go @@ -1,13 +1,13 @@ package db import ( + "cmp" "encoding/json" "errors" "fmt" "net/netip" "regexp" "slices" - "sort" "strconv" "strings" "sync" @@ -20,7 +20,6 @@ import ( "gorm.io/gorm" "tailscale.com/net/tsaddr" "tailscale.com/types/key" - "tailscale.com/types/ptr" ) const ( @@ -60,7 +59,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 } @@ -668,7 +667,7 @@ func (hsdb *HSDatabase) CreateNodeForTest(user *types.User, hostname ...string) Hostname: nodeName, UserID: &user.ID, RegisterMethod: util.RegisterMethodAuthKey, - AuthKeyID: ptr.To(pak.ID), + AuthKeyID: new(pak.ID), } err = hsdb.DB.Save(node).Error diff --git a/hscontrol/db/preauth_keys_test.go b/hscontrol/db/preauth_keys_test.go index 7c5dcbd7..2f28d449 100644 --- a/hscontrol/db/preauth_keys_test.go +++ b/hscontrol/db/preauth_keys_test.go @@ -11,7 +11,6 @@ import ( "github.com/juanfont/headscale/hscontrol/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "tailscale.com/types/ptr" ) func TestCreatePreAuthKey(t *testing.T) { @@ -24,7 +23,7 @@ func TestCreatePreAuthKey(t *testing.T) { test: func(t *testing.T, db *HSDatabase) { t.Helper() - _, err := db.CreatePreAuthKey(ptr.To(types.UserID(12345)), true, false, nil, nil) + _, err := db.CreatePreAuthKey(new(types.UserID(12345)), true, false, nil, nil) assert.Error(t, err) }, }, @@ -127,7 +126,7 @@ func TestCannotDeleteAssignedPreAuthKey(t *testing.T) { Hostname: "testest", UserID: &user.ID, RegisterMethod: util.RegisterMethodAuthKey, - AuthKeyID: ptr.To(key.ID), + AuthKeyID: new(key.ID), } db.DB.Save(&node) diff --git a/hscontrol/mapper/builder.go b/hscontrol/mapper/builder.go index c666ff24..b6f0b534 100644 --- a/hscontrol/mapper/builder.go +++ b/hscontrol/mapper/builder.go @@ -1,9 +1,10 @@ package mapper import ( + "cmp" "errors" "net/netip" - "sort" + "slices" "time" "github.com/juanfont/headscale/hscontrol/policy" @@ -261,8 +262,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 diff --git a/hscontrol/policy/v2/policy.go b/hscontrol/policy/v2/policy.go index 54196e6b..042c2723 100644 --- a/hscontrol/policy/v2/policy.go +++ b/hscontrol/policy/v2/policy.go @@ -956,14 +956,7 @@ func (pm *PolicyManager) invalidateGlobalPolicyCache(newNodes views.Slice[types. // It will return a Owners list where all the Tag types have been resolved to their underlying Owners. func flattenTags(tagOwners TagOwners, tag Tag, visiting map[Tag]bool, chain []Tag) (Owners, error) { if visiting[tag] { - cycleStart := 0 - - for i, t := range chain { - if t == tag { - cycleStart = i - break - } - } + cycleStart := slices.Index(chain, tag) cycleTags := make([]string, len(chain[cycleStart:])) for i, t := range chain[cycleStart:] { diff --git a/hscontrol/types/change/change.go b/hscontrol/types/change/change.go index a76fb7c4..6913d7d9 100644 --- a/hscontrol/types/change/change.go +++ b/hscontrol/types/change/change.go @@ -333,7 +333,7 @@ func NodeOnline(nodeID types.NodeID) Change { PeerPatches: []*tailcfg.PeerChange{ { NodeID: nodeID.NodeID(), - Online: ptrTo(true), + Online: new(true), }, }, } @@ -346,7 +346,7 @@ func NodeOffline(nodeID types.NodeID) Change { PeerPatches: []*tailcfg.PeerChange{ { NodeID: nodeID.NodeID(), - Online: ptrTo(false), + Online: new(false), }, }, } @@ -366,8 +366,10 @@ func KeyExpiry(nodeID types.NodeID, expiry *time.Time) Change { } // ptrTo returns a pointer to the given value. +// +//go:fix inline func ptrTo[T any](v T) *T { - return &v + return new(v) } // High-level change constructors diff --git a/hscontrol/types/change/change_test.go b/hscontrol/types/change/change_test.go index 9f181dd6..dc2dd0af 100644 --- a/hscontrol/types/change/change_test.go +++ b/hscontrol/types/change/change_test.go @@ -16,8 +16,8 @@ func TestChange_FieldSync(t *testing.T) { typ := reflect.TypeFor[Change]() boolCount := 0 - for i := range typ.NumField() { - if typ.Field(i).Type.Kind() == reflect.Bool { + for field := range typ.Fields() { + if field.Type.Kind() == reflect.Bool { boolCount++ } } diff --git a/integration/auth_oidc_test.go b/integration/auth_oidc_test.go index 359dd456..c1d066f8 100644 --- a/integration/auth_oidc_test.go +++ b/integration/auth_oidc_test.go @@ -1,15 +1,16 @@ package integration import ( + "cmp" "maps" "net/netip" "net/url" - "sort" + "slices" "strconv" "testing" "time" - "github.com/google/go-cmp/cmp" + gocmp "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" @@ -111,11 +112,11 @@ func TestOIDCAuthenticationPingAll(t *testing.T) { }, } - sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + slices.SortFunc(listUsers, func(a, b *v1.User) int { + return cmp.Compare(a.GetId(), b.GetId()) }) - if diff := cmp.Diff(want, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := gocmp.Diff(want, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { t.Fatalf("unexpected users: %s", diff) } } @@ -388,11 +389,11 @@ func TestOIDC024UserCreation(t *testing.T) { listUsers, err := headscale.ListUsers() require.NoError(t, err) - sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + slices.SortFunc(listUsers, func(a, b *v1.User) int { + return cmp.Compare(a.GetId(), b.GetId()) }) - if diff := cmp.Diff(want, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := gocmp.Diff(want, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { t.Errorf("unexpected users: %s", diff) } }) @@ -517,11 +518,11 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { }, } - sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + slices.SortFunc(listUsers, func(a, b *v1.User) int { + return cmp.Compare(a.GetId(), b.GetId()) }) - if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := gocmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { ct.Errorf("User validation failed after first login - unexpected users: %s", diff) } }, 30*time.Second, 1*time.Second, "validating user1 creation after initial OIDC login") @@ -599,11 +600,11 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { }, } - sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + slices.SortFunc(listUsers, func(a, b *v1.User) int { + return cmp.Compare(a.GetId(), b.GetId()) }) - if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := gocmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { ct.Errorf("User validation failed after user2 login - expected both user1 and user2: %s", diff) } }, 30*time.Second, 1*time.Second, "validating both user1 and user2 exist after second OIDC login") @@ -763,11 +764,11 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { }, } - sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + slices.SortFunc(listUsers, func(a, b *v1.User) int { + return cmp.Compare(a.GetId(), b.GetId()) }) - if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := gocmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { ct.Errorf("Final user validation failed - both users should persist after relogin cycle: %s", diff) } }, 30*time.Second, 1*time.Second, "validating user persistence after complete relogin cycle (user1->user2->user1)") @@ -935,13 +936,11 @@ func TestOIDCFollowUpUrl(t *testing.T) { }, } - sort.Slice( - listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() - }, - ) + slices.SortFunc(listUsers, func(a, b *v1.User) int { + return cmp.Compare(a.GetId(), b.GetId()) + }) - if diff := cmp.Diff( + if diff := gocmp.Diff( wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), @@ -1046,13 +1045,11 @@ func TestOIDCMultipleOpenedLoginUrls(t *testing.T) { }, } - sort.Slice( - listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() - }, - ) + slices.SortFunc(listUsers, func(a, b *v1.User) int { + return cmp.Compare(a.GetId(), b.GetId()) + }) - if diff := cmp.Diff( + if diff := gocmp.Diff( wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), @@ -1155,11 +1152,11 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) { }, } - sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + slices.SortFunc(listUsers, func(a, b *v1.User) int { + return cmp.Compare(a.GetId(), b.GetId()) }) - if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := gocmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { ct.Errorf("User validation failed after first login - unexpected users: %s", diff) } }, 30*time.Second, 1*time.Second, "validating user1 creation after initial OIDC login") @@ -1249,11 +1246,11 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) { }, } - sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + slices.SortFunc(listUsers, func(a, b *v1.User) int { + return cmp.Compare(a.GetId(), b.GetId()) }) - if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := gocmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { ct.Errorf("Final user validation failed - user1 should persist after same-user relogin: %s", diff) } }, 30*time.Second, 1*time.Second, "validating user1 persistence after same-user OIDC relogin cycle") diff --git a/integration/helpers.go b/integration/helpers.go index 7d40c8e6..5acf4729 100644 --- a/integration/helpers.go +++ b/integration/helpers.go @@ -26,7 +26,6 @@ import ( "golang.org/x/exp/maps" "golang.org/x/exp/slices" "tailscale.com/tailcfg" - "tailscale.com/types/ptr" ) const ( @@ -839,32 +838,32 @@ func wildcard() policyv2.Alias { // usernamep returns a pointer to a Username as an Alias for policy v2 configurations. // Used in ACL rules to reference specific users in network access policies. func usernamep(name string) policyv2.Alias { - return ptr.To(policyv2.Username(name)) + return new(policyv2.Username(name)) } // hostp returns a pointer to a Host as an Alias for policy v2 configurations. // Used in ACL rules to reference specific hosts in network access policies. func hostp(name string) policyv2.Alias { - return ptr.To(policyv2.Host(name)) + return new(policyv2.Host(name)) } // groupp returns a pointer to a Group as an Alias for policy v2 configurations. // Used in ACL rules to reference user groups in network access policies. func groupp(name string) policyv2.Alias { - return ptr.To(policyv2.Group(name)) + return new(policyv2.Group(name)) } // tagp returns a pointer to a Tag as an Alias for policy v2 configurations. // Used in ACL rules to reference node tags in network access policies. func tagp(name string) policyv2.Alias { - return ptr.To(policyv2.Tag(name)) + return new(policyv2.Tag(name)) } // prefixp returns a pointer to a Prefix from a CIDR string for policy v2 configurations. // Converts CIDR notation to policy prefix format for network range specifications. func prefixp(cidr string) policyv2.Alias { prefix := netip.MustParsePrefix(cidr) - return ptr.To(policyv2.Prefix(prefix)) + return new(policyv2.Prefix(prefix)) } // aliasWithPorts creates an AliasWithPorts structure from an alias and port ranges. @@ -880,31 +879,31 @@ func aliasWithPorts(alias policyv2.Alias, ports ...tailcfg.PortRange) policyv2.A // usernameOwner returns a Username as an Owner for use in TagOwners policies. // Specifies which users can assign and manage specific tags in ACL configurations. func usernameOwner(name string) policyv2.Owner { - return ptr.To(policyv2.Username(name)) + return new(policyv2.Username(name)) } // groupOwner returns a Group as an Owner for use in TagOwners policies. // Specifies which groups can assign and manage specific tags in ACL configurations. func groupOwner(name string) policyv2.Owner { - return ptr.To(policyv2.Group(name)) + return new(policyv2.Group(name)) } // usernameApprover returns a Username as an AutoApprover for subnet route policies. // Specifies which users can automatically approve subnet route advertisements. func usernameApprover(name string) policyv2.AutoApprover { - return ptr.To(policyv2.Username(name)) + return new(policyv2.Username(name)) } // groupApprover returns a Group as an AutoApprover for subnet route policies. // Specifies which groups can automatically approve subnet route advertisements. func groupApprover(name string) policyv2.AutoApprover { - return ptr.To(policyv2.Group(name)) + return new(policyv2.Group(name)) } // tagApprover returns a Tag as an AutoApprover for subnet route policies. // Specifies which tagged nodes can automatically approve subnet route advertisements. func tagApprover(name string) policyv2.AutoApprover { - return ptr.To(policyv2.Tag(name)) + return new(policyv2.Tag(name)) } // oidcMockUser creates a MockUser for OIDC authentication testing. diff --git a/integration/hsic/hsic.go b/integration/hsic/hsic.go index 42bb8e93..202f2014 100644 --- a/integration/hsic/hsic.go +++ b/integration/hsic/hsic.go @@ -16,7 +16,7 @@ import ( "os" "path" "path/filepath" - "sort" + "slices" "strconv" "strings" "time" @@ -1232,8 +1232,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 diff --git a/integration/route_test.go b/integration/route_test.go index 0460b5ef..6d0a1be2 100644 --- a/integration/route_test.go +++ b/integration/route_test.go @@ -7,7 +7,6 @@ import ( "maps" "net/netip" "slices" - "sort" "strconv" "strings" "testing" @@ -287,11 +286,10 @@ func TestHASubnetRouterFailover(t *testing.T) { t.Logf("webservice: %s, %s", webip.String(), weburl) // Sort nodes by ID - sort.SliceStable(allClients, func(i, j int) bool { - statusI := allClients[i].MustStatus() - statusJ := allClients[j].MustStatus() - - return statusI.Self.ID < statusJ.Self.ID + slices.SortStableFunc(allClients, func(a, b TailscaleClient) int { + statusA := a.MustStatus() + statusB := b.MustStatus() + return cmp.Compare(statusA.Self.ID, statusB.Self.ID) }) // This is ok because the scenario makes users in order, so the three first @@ -1359,10 +1357,10 @@ func TestSubnetRouteACL(t *testing.T) { } // Sort nodes by ID - sort.SliceStable(allClients, func(i, j int) bool { - statusI := allClients[i].MustStatus() - statusJ := allClients[j].MustStatus() - return statusI.Self.ID < statusJ.Self.ID + slices.SortStableFunc(allClients, func(a, b TailscaleClient) int { + statusA := a.MustStatus() + statusB := b.MustStatus() + return cmp.Compare(statusA.Self.ID, statusB.Self.ID) }) subRouter1 := allClients[0] @@ -2403,11 +2401,10 @@ func TestAutoApproveMultiNetwork(t *testing.T) { t.Logf("webservice: %s, %s", webip.String(), weburl) // Sort nodes by ID - sort.SliceStable(allClients, func(i, j int) bool { - statusI := allClients[i].MustStatus() - statusJ := allClients[j].MustStatus() - - return statusI.Self.ID < statusJ.Self.ID + slices.SortStableFunc(allClients, func(a, b TailscaleClient) int { + statusA := a.MustStatus() + statusB := b.MustStatus() + return cmp.Compare(statusA.Self.ID, statusB.Self.ID) }) // This is ok because the scenario makes users in order, so the three first diff --git a/integration/tags_test.go b/integration/tags_test.go index 5dad36e5..91c771c4 100644 --- a/integration/tags_test.go +++ b/integration/tags_test.go @@ -1,7 +1,7 @@ package integration import ( - "sort" + "slices" "testing" "time" @@ -13,7 +13,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "tailscale.com/tailcfg" - "tailscale.com/types/ptr" ) const tagTestUser = "taguser" @@ -30,9 +29,9 @@ const tagTestUser = "taguser" func tagsTestPolicy() *policyv2.Policy { return &policyv2.Policy{ TagOwners: policyv2.TagOwners{ - "tag:valid-owned": policyv2.Owners{ptr.To(policyv2.Username(tagTestUser + "@"))}, - "tag:second": policyv2.Owners{ptr.To(policyv2.Username(tagTestUser + "@"))}, - "tag:valid-unowned": policyv2.Owners{ptr.To(policyv2.Username("other-user@"))}, + "tag:valid-owned": policyv2.Owners{new(policyv2.Username(tagTestUser + "@"))}, + "tag:second": policyv2.Owners{new(policyv2.Username(tagTestUser + "@"))}, + "tag:valid-unowned": policyv2.Owners{new(policyv2.Username("other-user@"))}, // Note: tag:nonexistent deliberately NOT defined }, ACLs: []policyv2.ACL{ @@ -51,11 +50,11 @@ func tagsEqual(actual, expected []string) bool { return false } - sortedActual := append([]string{}, actual...) - sortedExpected := append([]string{}, expected...) + sortedActual := slices.Clone(actual) + sortedExpected := slices.Clone(expected) - sort.Strings(sortedActual) - sort.Strings(sortedExpected) + slices.Sort(sortedActual) + slices.Sort(sortedExpected) for i := range sortedActual { if sortedActual[i] != sortedExpected[i] { @@ -69,11 +68,11 @@ func tagsEqual(actual, expected []string) bool { // assertNodeHasTagsWithCollect asserts that a node has exactly the expected tags (order-independent). func assertNodeHasTagsWithCollect(c *assert.CollectT, node *v1.Node, expectedTags []string) { actualTags := node.GetTags() - sortedActual := append([]string{}, actualTags...) - sortedExpected := append([]string{}, expectedTags...) + sortedActual := slices.Clone(actualTags) + sortedExpected := slices.Clone(expectedTags) - sort.Strings(sortedActual) - sort.Strings(sortedExpected) + slices.Sort(sortedActual) + slices.Sort(sortedExpected) assert.Equal(c, sortedExpected, sortedActual, "Node %s tags mismatch", node.GetName()) } @@ -102,11 +101,11 @@ func assertNodeSelfHasTagsWithCollect(c *assert.CollectT, client TailscaleClient } } - sortedActual := append([]string{}, actualTagsSlice...) - sortedExpected := append([]string{}, expectedTags...) + sortedActual := slices.Clone(actualTagsSlice) + sortedExpected := slices.Clone(expectedTags) - sort.Strings(sortedActual) - sort.Strings(sortedExpected) + slices.Sort(sortedActual) + slices.Sort(sortedExpected) assert.Equal(c, sortedExpected, sortedActual, "Client %s self tags mismatch", client.Hostname()) } @@ -2507,11 +2506,11 @@ func assertNetmapSelfHasTagsWithCollect(c *assert.CollectT, client TailscaleClie } } - sortedActual := append([]string{}, actualTagsSlice...) - sortedExpected := append([]string{}, expectedTags...) + sortedActual := slices.Clone(actualTagsSlice) + sortedExpected := slices.Clone(expectedTags) - sort.Strings(sortedActual) - sort.Strings(sortedExpected) + slices.Sort(sortedActual) + slices.Sort(sortedExpected) assert.Equal(c, sortedExpected, sortedActual, "Client %s netmap self tags mismatch", client.Hostname()) }