all: apply golangci-lint autofixes

Go 1.27 allows setting promoted fields of embedded structs directly in
composite literals, so gorm.Model wrappers go away. Plus strings.Cut,
errors.AsType, reflect.TypeAssert and one gofumpt nit.
This commit is contained in:
Kristoffer Dalby
2026-08-25 07:20:06 +00:00
committed by Kristoffer Dalby
parent 2b64b9fe3a
commit 2c76d5c5b7
29 changed files with 195 additions and 218 deletions
+1 -2
View File
@@ -9,7 +9,6 @@ import (
"github.com/danielgtaylor/huma/v2" "github.com/danielgtaylor/huma/v2"
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"gorm.io/gorm"
) )
func init() { func init() {
@@ -217,7 +216,7 @@ func listUsersFiltered(b Backend, in *listUsersInput) ([]types.User, error) {
return b.State.ListAllUsers() return b.State.ListAllUsers()
} }
return b.State.ListUsersWithFilter(&types.User{Model: gorm.Model{ID: uint(id)}}) return b.State.ListUsersWithFilter(&types.User{ID: uint(id)})
default: default:
return b.State.ListAllUsers() return b.State.ListAllUsers()
} }
+8 -8
View File
@@ -415,8 +415,8 @@ func TestConstraints(t *testing.T) {
name: "no-oidc-duplicate-username-and-id", name: "no-oidc-duplicate-username-and-id",
run: func(t *testing.T, db *gorm.DB) { //nolint:thelper run: func(t *testing.T, db *gorm.DB) { //nolint:thelper
user := types.User{ user := types.User{
Model: gorm.Model{ID: 1}, ID: 1,
Name: "user1", Name: "user1",
} }
user.ProviderIdentifier = sql.NullString{String: "http://test.com/user1", Valid: true} user.ProviderIdentifier = sql.NullString{String: "http://test.com/user1", Valid: true}
@@ -424,8 +424,8 @@ func TestConstraints(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
user = types.User{ user = types.User{
Model: gorm.Model{ID: 2}, ID: 2,
Name: "user1", Name: "user1",
} }
user.ProviderIdentifier = sql.NullString{String: "http://test.com/user1", Valid: true} user.ProviderIdentifier = sql.NullString{String: "http://test.com/user1", Valid: true}
@@ -437,8 +437,8 @@ func TestConstraints(t *testing.T) {
name: "no-oidc-duplicate-id", name: "no-oidc-duplicate-id",
run: func(t *testing.T, db *gorm.DB) { //nolint:thelper run: func(t *testing.T, db *gorm.DB) { //nolint:thelper
user := types.User{ user := types.User{
Model: gorm.Model{ID: 1}, ID: 1,
Name: "user1", Name: "user1",
} }
user.ProviderIdentifier = sql.NullString{String: "http://test.com/user1", Valid: true} user.ProviderIdentifier = sql.NullString{String: "http://test.com/user1", Valid: true}
@@ -446,8 +446,8 @@ func TestConstraints(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
user = types.User{ user = types.User{
Model: gorm.Model{ID: 2}, ID: 2,
Name: "user1.1", Name: "user1.1",
} }
user.ProviderIdentifier = sql.NullString{String: "http://test.com/user1", Valid: true} user.ProviderIdentifier = sql.NullString{String: "http://test.com/user1", Valid: true}
+1 -1
View File
@@ -72,7 +72,7 @@ func (TextSerialiser) Scan(ctx context.Context, field *schema.Field, dst reflect
ret := f.Call(args) ret := f.Call(args)
if !ret[0].IsNil() { if !ret[0].IsNil() {
if err, ok := ret[0].Interface().(error); ok { if err, ok := reflect.TypeAssert[error](ret[0]); ok {
return decodingError(field.Name, err) return decodingError(field.Name, err)
} }
} }
+1 -2
View File
@@ -167,8 +167,7 @@ func TestKeyHandler_UnsupportedCapVerDoesNotLeakKey(t *testing.T) {
// errorAsHTTPError is a small local helper that unwraps an [HTTPError] // errorAsHTTPError is a small local helper that unwraps an [HTTPError]
// from an error chain. // from an error chain.
func errorAsHTTPError(err error) (HTTPError, bool) { func errorAsHTTPError(err error) (HTTPError, bool) {
var h HTTPError if h, ok := errors.AsType[HTTPError](err); ok {
if errors.As(err, &h) {
return h, true return h, true
} }
+6 -7
View File
@@ -11,19 +11,18 @@ import (
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/views" "tailscale.com/types/views"
) )
func TestApproveRoutesWithPolicy_NeverRemovesApprovedRoutes(t *testing.T) { func TestApproveRoutesWithPolicy_NeverRemovesApprovedRoutes(t *testing.T) {
user1 := types.User{ user1 := types.User{
Model: gorm.Model{ID: 1}, ID: 1,
Name: "testuser", Name: "testuser",
} }
user2 := types.User{ user2 := types.User{
Model: gorm.Model{ID: 2}, ID: 2,
Name: "otheruser", Name: "otheruser",
} }
users := []types.User{user1, user2} users := []types.User{user1, user2}
@@ -293,8 +292,8 @@ func TestApproveRoutesWithPolicy_NilAndEmptyCases(t *testing.T) {
t.Run(fmt.Sprintf("%s-policy-index%d", tt.name, i), func(t *testing.T) { t.Run(fmt.Sprintf("%s-policy-index%d", tt.name, i), func(t *testing.T) {
// Create test user // Create test user
user := types.User{ user := types.User{
Model: gorm.Model{ID: 1}, ID: 1,
Name: "test", Name: "test",
} }
users := []types.User{user} users := []types.User{user}
@@ -10,7 +10,6 @@ import (
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
) )
@@ -156,8 +155,8 @@ func TestApproveRoutesWithPolicy_NeverRemovesRoutes(t *testing.T) {
t.Run(fmt.Sprintf("%s-policy-index%d", tt.name, i), func(t *testing.T) { t.Run(fmt.Sprintf("%s-policy-index%d", tt.name, i), func(t *testing.T) {
// Create test user // Create test user
user := types.User{ user := types.User{
Model: gorm.Model{ID: 1}, ID: 1,
Name: tt.nodeUser, Name: tt.nodeUser,
} }
users := []types.User{user} users := []types.User{user}
@@ -283,8 +282,8 @@ func TestApproveRoutesWithPolicy_EdgeCases(t *testing.T) {
t.Run(fmt.Sprintf("%s-policy-index%d", tt.name, i), func(t *testing.T) { t.Run(fmt.Sprintf("%s-policy-index%d", tt.name, i), func(t *testing.T) {
// Create test user // Create test user
user := types.User{ user := types.User{
Model: gorm.Model{ID: 1}, ID: 1,
Name: "test", Name: "test",
} }
users := []types.User{user} users := []types.User{user}
@@ -326,8 +325,8 @@ func TestApproveRoutesWithPolicy_EdgeCases(t *testing.T) {
func TestApproveRoutesWithPolicy_NilPolicyManagerCase(t *testing.T) { func TestApproveRoutesWithPolicy_NilPolicyManagerCase(t *testing.T) {
user := types.User{ user := types.User{
Model: gorm.Model{ID: 1}, ID: 1,
Name: "test", Name: "test",
} }
userID := user.ID userID := user.ID
+5 -6
View File
@@ -11,7 +11,6 @@ import (
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@@ -1134,11 +1133,11 @@ func TestReduceNodesFromPolicy(t *testing.T) {
func TestSSHPolicyRules(t *testing.T) { func TestSSHPolicyRules(t *testing.T) {
users := []types.User{ users := []types.User{
{Name: "user1", Model: gorm.Model{ID: 1}}, {Name: "user1", ID: 1},
{Name: "user2", Model: gorm.Model{ID: 2}}, {Name: "user2", ID: 2},
{Name: "user3", Model: gorm.Model{ID: 3}}, {Name: "user3", ID: 3},
{Name: "alice", Email: "alice@example.com", Model: gorm.Model{ID: 4}}, {Name: "alice", Email: "alice@example.com", ID: 4},
{Name: "bob", Email: "bob@example.com", Model: gorm.Model{ID: 5}}, {Name: "bob", Email: "bob@example.com", ID: 5},
} }
// Create standard node setups used across tests // Create standard node setups used across tests
+5 -6
View File
@@ -13,7 +13,6 @@ import (
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/util/must" "tailscale.com/util/must"
@@ -108,11 +107,11 @@ func TestTheInternet(t *testing.T) {
func TestReduceFilterRules(t *testing.T) { func TestReduceFilterRules(t *testing.T) {
users := types.Users{ users := types.Users{
types.User{Model: gorm.Model{ID: 1}, Name: "mickael"}, types.User{ID: 1, Name: "mickael"},
types.User{Model: gorm.Model{ID: 2}, Name: "user1"}, types.User{ID: 2, Name: "user1"},
types.User{Model: gorm.Model{ID: 3}, Name: "user2"}, types.User{ID: 3, Name: "user2"},
types.User{Model: gorm.Model{ID: 4}, Name: "user100"}, types.User{ID: 4, Name: "user100"},
types.User{Model: gorm.Model{ID: 5}, Name: "user3"}, types.User{ID: 5, Name: "user3"},
} }
tests := []struct { tests := []struct {
+3 -4
View File
@@ -9,14 +9,13 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
) )
func TestNodeCanApproveRoute(t *testing.T) { func TestNodeCanApproveRoute(t *testing.T) {
users := []types.User{ users := []types.User{
{Name: "user1", Model: gorm.Model{ID: 1}}, {Name: "user1", ID: 1},
{Name: "user2", Model: gorm.Model{ID: 2}}, {Name: "user2", ID: 2},
{Name: "user3", Model: gorm.Model{ID: 3}}, {Name: "user3", ID: 3},
} }
// Create standard node setups used across tests // Create standard node setups used across tests
+63 -64
View File
@@ -15,7 +15,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go4.org/netipx" "go4.org/netipx"
"gorm.io/gorm"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@@ -29,7 +28,7 @@ func aliasWithPorts(alias Alias, ports ...tailcfg.PortRange) AliasWithPorts {
func TestParsing(t *testing.T) { func TestParsing(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "testuser"}, {ID: 1, Name: "testuser"},
} }
tests := []struct { tests := []struct {
name string name string
@@ -390,8 +389,8 @@ func TestParsing(t *testing.T) {
func TestCompileSSHPolicy_UserMapping(t *testing.T) { func TestCompileSSHPolicy_UserMapping(t *testing.T) {
users := types.Users{ users := types.Users{
{Name: "user1", Model: gorm.Model{ID: 1}}, {Name: "user1", ID: 1},
{Name: "user2", Model: gorm.Model{ID: 2}}, {Name: "user2", ID: 2},
} }
// Create test nodes - use tagged nodes as SSH destinations // Create test nodes - use tagged nodes as SSH destinations
@@ -638,10 +637,10 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) {
func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) { func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
users := types.Users{ users := types.Users{
{Name: "alice", Email: "alice@example.com", Model: gorm.Model{ID: 1}}, {Name: "alice", Email: "alice@example.com", ID: 1},
{Name: "bob", Email: "bob@example.com", Model: gorm.Model{ID: 2}}, {Name: "bob", Email: "bob@example.com", ID: 2},
{Name: "charlie", Email: "charlie@other.com", Model: gorm.Model{ID: 3}}, {Name: "charlie", Email: "charlie@other.com", ID: 3},
{Name: "dave", Model: gorm.Model{ID: 4}}, // CLI user, no email {Name: "dave", ID: 4}, // CLI user, no email
} }
nodeTaggedServer := types.Node{ nodeTaggedServer := types.Node{
@@ -836,10 +835,10 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
{ {
name: "localpart with special chars in email", name: "localpart with special chars in email",
users: types.Users{ users: types.Users{
{Name: "dave+sshuser", Email: "dave+sshuser@example.com", Model: gorm.Model{ID: 10}}, {Name: "dave+sshuser", Email: "dave+sshuser@example.com", ID: 10},
}, },
nodes: func() types.Nodes { nodes: func() types.Nodes {
specialUser := types.User{Name: "dave+sshuser", Email: "dave+sshuser@example.com", Model: gorm.Model{ID: 10}} specialUser := types.User{Name: "dave+sshuser", Email: "dave+sshuser@example.com", ID: 10}
n := types.Node{ n := types.Node{
Hostname: "special-device", Hostname: "special-device",
IPv4: createAddr("100.64.0.10"), IPv4: createAddr("100.64.0.10"),
@@ -880,10 +879,10 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
{ {
name: "localpart excludes CLI users without email", name: "localpart excludes CLI users without email",
users: types.Users{ users: types.Users{
{Name: "dave", Model: gorm.Model{ID: 4}}, {Name: "dave", ID: 4},
}, },
nodes: func() types.Nodes { nodes: func() types.Nodes {
cliUser := types.User{Name: "dave", Model: gorm.Model{ID: 4}} cliUser := types.User{Name: "dave", ID: 4}
n := types.Node{ n := types.Node{
Hostname: "dave-cli-device", Hostname: "dave-cli-device",
IPv4: createAddr("100.64.0.5"), IPv4: createAddr("100.64.0.5"),
@@ -1005,8 +1004,8 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
func TestCompileSSHPolicy_CheckAction(t *testing.T) { func TestCompileSSHPolicy_CheckAction(t *testing.T) {
users := types.Users{ users := types.Users{
{Name: "user1", Model: gorm.Model{ID: 1}}, {Name: "user1", ID: 1},
{Name: "user2", Model: gorm.Model{ID: 2}}, {Name: "user2", ID: 2},
} }
// Use tagged nodes for SSH user mapping tests // Use tagged nodes for SSH user mapping tests
@@ -1077,8 +1076,8 @@ func TestCompileSSHPolicy_CheckAction(t *testing.T) {
// the accept rule appears first in the policy definition. // the accept rule appears first in the policy definition.
func TestCompileSSHPolicy_CheckBeforeAcceptOrdering(t *testing.T) { func TestCompileSSHPolicy_CheckBeforeAcceptOrdering(t *testing.T) {
users := types.Users{ users := types.Users{
{Name: "user1", Model: gorm.Model{ID: 1}}, {Name: "user1", ID: 1},
{Name: "user2", Model: gorm.Model{ID: 2}}, {Name: "user2", ID: 2},
} }
nodeTaggedServer := types.Node{ nodeTaggedServer := types.Node{
@@ -1153,8 +1152,8 @@ func TestCompileSSHPolicy_CheckBeforeAcceptOrdering(t *testing.T) {
func TestSSHIntegrationReproduction(t *testing.T) { func TestSSHIntegrationReproduction(t *testing.T) {
// Create users matching the integration test // Create users matching the integration test
users := types.Users{ users := types.Users{
{Name: "user1", Model: gorm.Model{ID: 1}}, {Name: "user1", ID: 1},
{Name: "user2", Model: gorm.Model{ID: 2}}, {Name: "user2", ID: 2},
} }
// Create simple nodes for testing // Create simple nodes for testing
@@ -1218,7 +1217,7 @@ func TestSSHIntegrationReproduction(t *testing.T) {
// to JSON and that the sshUsers field is not empty. // to JSON and that the sshUsers field is not empty.
func TestSSHJSONSerialization(t *testing.T) { func TestSSHJSONSerialization(t *testing.T) {
users := types.Users{ users := types.Users{
{Name: "user1", Model: gorm.Model{ID: 1}}, {Name: "user1", ID: 1},
} }
uid := uint(1) uid := uint(1)
@@ -1278,8 +1277,8 @@ func TestSSHJSONSerialization(t *testing.T) {
func TestCompileFilterRulesForNodeWithAutogroupSelf(t *testing.T) { func TestCompileFilterRulesForNodeWithAutogroupSelf(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -1439,8 +1438,8 @@ func TestCompileFilterRulesForNodeWithAutogroupSelf(t *testing.T) {
// It also verifies that tag-to-tag rules work correctly. // It also verifies that tag-to-tag rules work correctly.
func TestTagUserMutualExclusivity(t *testing.T) { func TestTagUserMutualExclusivity(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -1541,8 +1540,8 @@ func TestTagUserMutualExclusivity(t *testing.T) {
// are valid and should produce filter rules. // are valid and should produce filter rules.
func TestUserToTagCrossIdentityGrant(t *testing.T) { func TestUserToTagCrossIdentityGrant(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -1626,8 +1625,8 @@ func TestAutogroupTagged(t *testing.T) {
t.Parallel() t.Parallel()
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -1778,8 +1777,8 @@ func TestAutogroupSelfInSourceIsRejected(t *testing.T) {
// are allowed (and only if they match the target user). // are allowed (and only if they match the target user).
func TestAutogroupSelfWithSpecificUserSource(t *testing.T) { func TestAutogroupSelfWithSpecificUserSource(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -1845,9 +1844,9 @@ func TestAutogroupSelfWithSpecificUserSource(t *testing.T) {
// as the target are allowed. // as the target are allowed.
func TestAutogroupSelfWithGroupSource(t *testing.T) { func TestAutogroupSelfWithGroupSource(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
{Model: gorm.Model{ID: 3}, Name: "user3"}, {ID: 3, Name: "user3"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -1914,8 +1913,8 @@ func createAddr(ip string) *netip.Addr {
// with autogroup:self in destinations. // with autogroup:self in destinations.
func TestSSHWithAutogroupSelfInDestination(t *testing.T) { func TestSSHWithAutogroupSelfInDestination(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -1996,8 +1995,8 @@ func TestSSHWithAutogroupSelfInDestination(t *testing.T) {
// can SSH (and only if they match the target user). // can SSH (and only if they match the target user).
func TestSSHWithAutogroupSelfAndSpecificUser(t *testing.T) { func TestSSHWithAutogroupSelfAndSpecificUser(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -2051,9 +2050,9 @@ func TestSSHWithAutogroupSelfAndSpecificUser(t *testing.T) {
// TestSSHWithAutogroupSelfAndGroup verifies SSH with group sources and autogroup:self destinations. // TestSSHWithAutogroupSelfAndGroup verifies SSH with group sources and autogroup:self destinations.
func TestSSHWithAutogroupSelfAndGroup(t *testing.T) { func TestSSHWithAutogroupSelfAndGroup(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
{Model: gorm.Model{ID: 3}, Name: "user3"}, {ID: 3, Name: "user3"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -2112,7 +2111,7 @@ func TestSSHWithAutogroupSelfAndGroup(t *testing.T) {
// are excluded from both sources and destinations when autogroup:self is used. // are excluded from both sources and destinations when autogroup:self is used.
func TestSSHWithAutogroupSelfExcludesTaggedDevices(t *testing.T) { func TestSSHWithAutogroupSelfExcludesTaggedDevices(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -2173,8 +2172,8 @@ func TestSSHWithAutogroupSelfExcludesTaggedDevices(t *testing.T) {
// autogroup:self filtering only applies to autogroup:self destinations, not others. // autogroup:self filtering only applies to autogroup:self destinations, not others.
func TestSSHWithAutogroupSelfAndMixedDestinations(t *testing.T) { func TestSSHWithAutogroupSelfAndMixedDestinations(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -2246,9 +2245,9 @@ func TestSSHWithAutogroupSelfAndMixedDestinations(t *testing.T) {
// registered nodes. // registered nodes.
func TestAutogroupSelfWithNonExistentUserInGroup(t *testing.T) { func TestAutogroupSelfWithNonExistentUserInGroup(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "superadmin"}, {ID: 1, Name: "superadmin"},
{Model: gorm.Model{ID: 2}, Name: "admin"}, {ID: 2, Name: "admin"},
{Model: gorm.Model{ID: 3}, Name: "direction"}, {ID: 3, Name: "direction"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -2600,7 +2599,7 @@ func TestMergeFilterRules(t *testing.T) {
func TestCompileSSHPolicy_CheckPeriodVariants(t *testing.T) { func TestCompileSSHPolicy_CheckPeriodVariants(t *testing.T) {
users := types.Users{ users := types.Users{
{Name: "user1", Model: gorm.Model{ID: 1}}, {Name: "user1", ID: 1},
} }
node := types.Node{ node := types.Node{
@@ -2755,8 +2754,8 @@ func TestIPSetToPrincipals(t *testing.T) {
func TestSSHCheckParams(t *testing.T) { func TestSSHCheckParams(t *testing.T) {
users := types.Users{ users := types.Users{
{Name: "user1", Model: gorm.Model{ID: 1}}, {Name: "user1", ID: 1},
{Name: "user2", Model: gorm.Model{ID: 2}}, {Name: "user2", ID: 2},
} }
nodeUser1 := types.Node{ nodeUser1 := types.Node{
@@ -2944,14 +2943,14 @@ func TestResolveLocalparts(t *testing.T) {
{ {
name: "no entries", name: "no entries",
entries: nil, entries: nil,
users: types.Users{{Name: "alice", Email: "alice@example.com", Model: gorm.Model{ID: 1}}}, users: types.Users{{Name: "alice", Email: "alice@example.com", ID: 1}},
want: nil, want: nil,
}, },
{ {
name: "single match", name: "single match",
entries: []SSHUser{"localpart:*@example.com"}, entries: []SSHUser{"localpart:*@example.com"},
users: types.Users{ users: types.Users{
{Name: "alice", Email: "alice@example.com", Model: gorm.Model{ID: 1}}, {Name: "alice", Email: "alice@example.com", ID: 1},
}, },
want: map[uint]string{1: "alice"}, want: map[uint]string{1: "alice"},
}, },
@@ -2959,7 +2958,7 @@ func TestResolveLocalparts(t *testing.T) {
name: "domain mismatch", name: "domain mismatch",
entries: []SSHUser{"localpart:*@other.com"}, entries: []SSHUser{"localpart:*@other.com"},
users: types.Users{ users: types.Users{
{Name: "alice", Email: "alice@example.com", Model: gorm.Model{ID: 1}}, {Name: "alice", Email: "alice@example.com", ID: 1},
}, },
want: map[uint]string{}, want: map[uint]string{},
}, },
@@ -2967,7 +2966,7 @@ func TestResolveLocalparts(t *testing.T) {
name: "case insensitive domain", name: "case insensitive domain",
entries: []SSHUser{"localpart:*@EXAMPLE.COM"}, entries: []SSHUser{"localpart:*@EXAMPLE.COM"},
users: types.Users{ users: types.Users{
{Name: "alice", Email: "alice@example.com", Model: gorm.Model{ID: 1}}, {Name: "alice", Email: "alice@example.com", ID: 1},
}, },
want: map[uint]string{1: "alice"}, want: map[uint]string{1: "alice"},
}, },
@@ -2975,7 +2974,7 @@ func TestResolveLocalparts(t *testing.T) {
name: "user without email skipped", name: "user without email skipped",
entries: []SSHUser{"localpart:*@example.com"}, entries: []SSHUser{"localpart:*@example.com"},
users: types.Users{ users: types.Users{
{Name: "cli-user", Model: gorm.Model{ID: 1}}, {Name: "cli-user", ID: 1},
}, },
want: map[uint]string{}, want: map[uint]string{},
}, },
@@ -2986,9 +2985,9 @@ func TestResolveLocalparts(t *testing.T) {
"localpart:*@other.com", "localpart:*@other.com",
}, },
users: types.Users{ users: types.Users{
{Name: "alice", Email: "alice@example.com", Model: gorm.Model{ID: 1}}, {Name: "alice", Email: "alice@example.com", ID: 1},
{Name: "bob", Email: "bob@other.com", Model: gorm.Model{ID: 2}}, {Name: "bob", Email: "bob@other.com", ID: 2},
{Name: "charlie", Email: "charlie@nope.com", Model: gorm.Model{ID: 3}}, {Name: "charlie", Email: "charlie@nope.com", ID: 3},
}, },
want: map[uint]string{1: "alice", 2: "bob"}, want: map[uint]string{1: "alice", 2: "bob"},
}, },
@@ -2996,7 +2995,7 @@ func TestResolveLocalparts(t *testing.T) {
name: "special chars in local part", name: "special chars in local part",
entries: []SSHUser{"localpart:*@example.com"}, entries: []SSHUser{"localpart:*@example.com"},
users: types.Users{ users: types.Users{
{Name: "d", Email: "dave+ssh@example.com", Model: gorm.Model{ID: 1}}, {Name: "d", Email: "dave+ssh@example.com", ID: 1},
}, },
want: map[uint]string{1: "dave+ssh"}, want: map[uint]string{1: "dave+ssh"},
}, },
@@ -3015,11 +3014,11 @@ func TestResolveLocalparts(t *testing.T) {
func TestGroupSourcesByUser(t *testing.T) { func TestGroupSourcesByUser(t *testing.T) {
alice := types.User{ alice := types.User{
Name: "alice", Email: "alice@example.com", Name: "alice", Email: "alice@example.com",
Model: gorm.Model{ID: 1}, ID: 1,
} }
bob := types.User{ bob := types.User{
Name: "bob", Email: "bob@example.com", Name: "bob", Email: "bob@example.com",
Model: gorm.Model{ID: 2}, ID: 2,
} }
nodeAlice := types.Node{ nodeAlice := types.Node{
@@ -3481,7 +3480,7 @@ func TestFilterAllowAllFix(t *testing.T) {
t.Parallel() t.Parallel()
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "testuser"}, {ID: 1, Name: "testuser"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
&types.Node{ &types.Node{
@@ -3586,7 +3585,7 @@ func TestCompileViaGrant(t *testing.T) {
t.Parallel() t.Parallel()
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "testuser"}, {ID: 1, Name: "testuser"},
} }
allPorts := []ProtocolPort{ allPorts := []ProtocolPort{
@@ -3882,8 +3881,8 @@ func TestCompileGrantWithAutogroupSelf_GrantPaths(t *testing.T) {
t.Parallel() t.Parallel()
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
} }
node1 := &types.Node{ node1 := &types.Node{
@@ -4089,7 +4088,7 @@ func TestDestinationsToNetPortRange_AutogroupInternet(t *testing.T) {
t.Parallel() t.Parallel()
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "testuser"}, {ID: 1, Name: "testuser"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
&types.Node{ &types.Node{
+1 -1
View File
@@ -148,7 +148,7 @@ func expectedExitPeerVisibility(
var seen []string var seen []string
for _, peer := range capture.Netmap.Peers { for _, peer := range capture.Netmap.Peers {
peerName := strings.Split(peer.Name(), ".")[0] peerName, _, _ := strings.Cut(peer.Name(), ".")
if !exitAdvertisers[peerName] { if !exitAdvertisers[peerName] {
continue continue
+2 -3
View File
@@ -12,7 +12,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@@ -25,8 +24,8 @@ func TestIssue3233ViaInternetExitVisibility(t *testing.T) {
t.Parallel() t.Parallel()
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "alice", Email: "alice@headscale.net"}, {ID: 1, Name: "alice", Email: "alice@headscale.net"},
{Model: gorm.Model{ID: 2}, Name: "bob", Email: "bob@headscale.net"}, {ID: 2, Name: "bob", Email: "bob@headscale.net"},
} }
exitRoutes := []netip.Prefix{tsaddr.AllIPv4(), tsaddr.AllIPv6()} exitRoutes := []netip.Prefix{tsaddr.AllIPv4(), tsaddr.AllIPv6()}
+1 -2
View File
@@ -7,7 +7,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@@ -27,7 +26,7 @@ func TestIssue3267ViaGrantBroaderDestination(t *testing.T) {
t.Parallel() t.Parallel()
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "alice", Email: issue3267AliceEmail}, //nolint:goconst {ID: 1, Name: "alice", Email: issue3267AliceEmail}, //nolint:goconst
} }
cases := []struct { cases := []struct {
+1 -2
View File
@@ -6,7 +6,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@@ -19,7 +18,7 @@ import (
// as not needing a recompute. // as not needing a recompute.
func TestNodeNeedsPeerRecompute(t *testing.T) { func TestNodeNeedsPeerRecompute(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@headscale.net"}, {ID: 1, Name: "user1", Email: "user1@headscale.net"},
} }
const allowAll = `{"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}` const allowAll = `{"acls":[{"action":"accept","src":["*"],"dst":["*:*"]}]}`
+2 -3
View File
@@ -10,7 +10,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@@ -19,8 +18,8 @@ import (
// resolution is exercised across both. // resolution is exercised across both.
func nodeAttrsTestUsers() types.Users { func nodeAttrsTestUsers() types.Users {
return types.Users{ return types.Users{
{Model: gorm.Model{ID: 1}, Name: "alice", Email: "alice@example.com"}, {ID: 1, Name: "alice", Email: "alice@example.com"},
{Model: gorm.Model{ID: 2}, Name: "bob", Email: "bob@example.org"}, {ID: 2, Name: "bob", Email: "bob@example.org"},
} }
} }
@@ -8,7 +8,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
) )
// TestPolicyManagerConcurrentReads is the correctness guard for the #3346 fix: // TestPolicyManagerConcurrentReads is the correctness guard for the #3346 fix:
@@ -22,9 +21,9 @@ import (
// the lazy caches are written. // the lazy caches are written.
func TestPolicyManagerConcurrentReads(t *testing.T) { func TestPolicyManagerConcurrentReads(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@headscale.net"}, {ID: 1, Name: "user1", Email: "user1@headscale.net"},
{Model: gorm.Model{ID: 2}, Name: "user2", Email: "user2@headscale.net"}, {ID: 2, Name: "user2", Email: "user2@headscale.net"},
{Model: gorm.Model{ID: 3}, Name: "user3", Email: "user3@headscale.net"}, {ID: 3, Name: "user3", Email: "user3@headscale.net"},
} }
policy := `{ policy := `{
+42 -43
View File
@@ -10,7 +10,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/puzpuzpuz/xsync/v4" "github.com/puzpuzpuz/xsync/v4"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@@ -28,8 +27,8 @@ func node(name, ipv4, ipv6 string, user types.User) *types.Node {
func TestPolicyManager(t *testing.T) { func TestPolicyManager(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "testuser", Email: "testuser@headscale.net"}, {ID: 1, Name: "testuser", Email: "testuser@headscale.net"},
{Model: gorm.Model{ID: 2}, Name: "otheruser", Email: "otheruser@headscale.net"}, {ID: 2, Name: "otheruser", Email: "otheruser@headscale.net"},
} }
tests := []struct { tests := []struct {
@@ -87,9 +86,9 @@ func TestPolicyManager(t *testing.T) {
func TestInvalidateAutogroupSelfCache(t *testing.T) { func TestInvalidateAutogroupSelfCache(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@headscale.net"}, {ID: 1, Name: "user1", Email: "user1@headscale.net"},
{Model: gorm.Model{ID: 2}, Name: "user2", Email: "user2@headscale.net"}, {ID: 2, Name: "user2", Email: "user2@headscale.net"},
{Model: gorm.Model{ID: 3}, Name: "user3", Email: "user3@headscale.net"}, {ID: 3, Name: "user3", Email: "user3@headscale.net"},
} }
//nolint:goconst // test-specific inline policy for clarity //nolint:goconst // test-specific inline policy for clarity
@@ -237,8 +236,8 @@ func TestInvalidateAutogroupSelfCache(t *testing.T) {
// owning user from UserID, not from the User view. // owning user from UserID, not from the User view.
func TestSetNodesAutogroupSelfUnhydratedUser(t *testing.T) { func TestSetNodesAutogroupSelfUnhydratedUser(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@headscale.net"}, {ID: 1, Name: "user1", Email: "user1@headscale.net"},
{Model: gorm.Model{ID: 2}, Name: "user2", Email: "user2@headscale.net"}, {ID: 2, Name: "user2", Email: "user2@headscale.net"},
} }
policy := `{ policy := `{
@@ -305,7 +304,7 @@ func TestSetNodesAutogroupSelfUnhydratedUser(t *testing.T) {
// autogroup:self destination is active. // autogroup:self destination is active.
func TestSSHCheckParamsUnhydratedUserNoPanic(t *testing.T) { func TestSSHCheckParamsUnhydratedUserNoPanic(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@headscale.net"}, {ID: 1, Name: "user1", Email: "user1@headscale.net"},
} }
policy := `{ policy := `{
@@ -523,8 +522,8 @@ func TestInvalidateGlobalPolicyCache(t *testing.T) {
// 1. BuildPeerMap uses unreduced compiled rules for determining peer relationships // 1. BuildPeerMap uses unreduced compiled rules for determining peer relationships
// 2. FilterForNode returns reduced compiled rules for packet filters. // 2. FilterForNode returns reduced compiled rules for packet filters.
func TestAutogroupSelfReducedVsUnreducedRules(t *testing.T) { func TestAutogroupSelfReducedVsUnreducedRules(t *testing.T) {
user1 := types.User{Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@headscale.net"} user1 := types.User{ID: 1, Name: "user1", Email: "user1@headscale.net"}
user2 := types.User{Model: gorm.Model{ID: 2}, Name: "user2", Email: "user2@headscale.net"} user2 := types.User{ID: 2, Name: "user2", Email: "user2@headscale.net"}
users := types.Users{user1, user2} users := types.Users{user1, user2}
// Create two nodes // Create two nodes
@@ -600,8 +599,8 @@ func TestAutogroupSelfReducedVsUnreducedRules(t *testing.T) {
// This ensures that autogroup:self doesn't interfere with other ACL rules. // This ensures that autogroup:self doesn't interfere with other ACL rules.
func TestAutogroupSelfWithOtherRules(t *testing.T) { func TestAutogroupSelfWithOtherRules(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "test-1", Email: "test-1@example.com"}, {ID: 1, Name: "test-1", Email: "test-1@example.com"},
{Model: gorm.Model{ID: 2}, Name: "test-2", Email: "test-2@example.com"}, {ID: 2, Name: "test-2", Email: "test-2@example.com"},
} }
// test-1 has a regular device // test-1 has a regular device
@@ -681,8 +680,8 @@ func TestAutogroupSelfWithOtherRules(t *testing.T) {
// leaving nodes with stale filter rules until reconnect. // leaving nodes with stale filter rules until reconnect.
func TestAutogroupSelfPolicyUpdateTriggersMapResponse(t *testing.T) { func TestAutogroupSelfPolicyUpdateTriggersMapResponse(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "test-1", Email: "test-1@example.com"}, {ID: 1, Name: "test-1", Email: "test-1@example.com"},
{Model: gorm.Model{ID: 2}, Name: "test-2", Email: "test-2@example.com"}, {ID: 2, Name: "test-2", Email: "test-2@example.com"},
} }
test1Node := &types.Node{ test1Node := &types.Node{
@@ -765,8 +764,8 @@ func TestAutogroupSelfPolicyUpdateTriggersMapResponse(t *testing.T) {
// https://github.com/juanfont/headscale/issues/2389 // https://github.com/juanfont/headscale/issues/2389
func TestTagPropagationToPeerMap(t *testing.T) { func TestTagPropagationToPeerMap(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@headscale.net"}, {ID: 1, Name: "user1", Email: "user1@headscale.net"},
{Model: gorm.Model{ID: 2}, Name: "user2", Email: "user2@headscale.net"}, {ID: 2, Name: "user2", Email: "user2@headscale.net"},
} }
// Policy: user2 can access tag:web nodes // Policy: user2 can access tag:web nodes
@@ -893,8 +892,8 @@ func TestTagPropagationToPeerMap(t *testing.T) {
// BOTH admin and tagged node should see each other as peers. // BOTH admin and tagged node should see each other as peers.
func TestAutogroupSelfWithAdminOverride(t *testing.T) { func TestAutogroupSelfWithAdminOverride(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "admin", Email: "admin@example.com"}, {ID: 1, Name: "admin", Email: "admin@example.com"},
{Model: gorm.Model{ID: 2}, Name: "user1", Email: "user1@example.com"}, {ID: 2, Name: "user1", Email: "user1@example.com"},
} }
// Admin has a regular device // Admin has a regular device
@@ -976,8 +975,8 @@ func TestAutogroupSelfWithAdminOverride(t *testing.T) {
// This is the same behavior as the global filter path. // This is the same behavior as the global filter path.
func TestAutogroupSelfSymmetricVisibility(t *testing.T) { func TestAutogroupSelfSymmetricVisibility(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@example.com"}, {ID: 1, Name: "user1", Email: "user1@example.com"},
{Model: gorm.Model{ID: 2}, Name: "user2", Email: "user2@example.com"}, {ID: 2, Name: "user2", Email: "user2@example.com"},
} }
// user1 has device A // user1 has device A
@@ -1059,10 +1058,10 @@ func TestAutogroupSelfSymmetricVisibility(t *testing.T) {
// - All tagged nodes should be visible to users who can access them. // - All tagged nodes should be visible to users who can access them.
func TestAutogroupSelfDoesNotBreakOtherUsersAccess(t *testing.T) { func TestAutogroupSelfDoesNotBreakOtherUsersAccess(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "superadmin", Email: "superadmin@example.com"}, {ID: 1, Name: "superadmin", Email: "superadmin@example.com"},
{Model: gorm.Model{ID: 2}, Name: "admin", Email: "admin@example.com"}, {ID: 2, Name: "admin", Email: "admin@example.com"},
{Model: gorm.Model{ID: 3}, Name: "direction", Email: "direction@example.com"}, {ID: 3, Name: "direction", Email: "direction@example.com"},
{Model: gorm.Model{ID: 4}, Name: "tagowner", Email: "tagowner@example.com"}, {ID: 4, Name: "tagowner", Email: "tagowner@example.com"},
} }
// Create nodes: // Create nodes:
@@ -1229,8 +1228,8 @@ func TestAutogroupSelfDoesNotBreakOtherUsersAccess(t *testing.T) {
// visible to nodes that can access them. // visible to nodes that can access them.
func TestEmptyFilterNodesStillVisible(t *testing.T) { func TestEmptyFilterNodesStillVisible(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "admin", Email: "admin@example.com"}, {ID: 1, Name: "admin", Email: "admin@example.com"},
{Model: gorm.Model{ID: 2}, Name: "tagowner", Email: "tagowner@example.com"}, {ID: 2, Name: "tagowner", Email: "tagowner@example.com"},
} }
adminDevice := &types.Node{ adminDevice := &types.Node{
@@ -1297,8 +1296,8 @@ func TestEmptyFilterNodesStillVisible(t *testing.T) {
// tagged nodes AND their own devices. // tagged nodes AND their own devices.
func TestAutogroupSelfCombinedWithTags(t *testing.T) { func TestAutogroupSelfCombinedWithTags(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "admin", Email: "admin@example.com"}, {ID: 1, Name: "admin", Email: "admin@example.com"},
{Model: gorm.Model{ID: 2}, Name: "tagowner", Email: "tagowner@example.com"}, {ID: 2, Name: "tagowner", Email: "tagowner@example.com"},
} }
// Admin has two devices // Admin has two devices
@@ -1393,7 +1392,7 @@ func TestAutogroupSelfCombinedWithTags(t *testing.T) {
// Expected: node1 should be able to reach node2 via group:admin -> *:* rule. // Expected: node1 should be able to reach node2 via group:admin -> *:* rule.
func TestIssue2990SameUserTaggedDevice(t *testing.T) { func TestIssue2990SameUserTaggedDevice(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@"}, {ID: 1, Name: "user1", Email: "user1@"},
} }
// node1: user device (not tagged), belongs to user1 // node1: user device (not tagged), belongs to user1
@@ -1493,8 +1492,8 @@ func TestViaRoutesForPeer(t *testing.T) {
t.Parallel() t.Parallel()
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1", Email: "user1@"}, {ID: 1, Name: "user1", Email: "user1@"},
{Model: gorm.Model{ID: 2}, Name: "user2", Email: "user2@"}, {ID: 2, Name: "user2", Email: "user2@"},
} }
t.Run("self_returns_empty", func(t *testing.T) { t.Run("self_returns_empty", func(t *testing.T) {
@@ -2102,7 +2101,7 @@ func TestBuildPeerMap_AutogroupInternetMakesExitNodeVisible(t *testing.T) {
t.Parallel() t.Parallel()
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "alice", Email: "alice@headscale.net"}, {ID: 1, Name: "alice", Email: "alice@headscale.net"},
} }
aliceNode := node("alice-laptop", "100.64.0.10", "fd7a:115c:a1e0::a", users[0]) aliceNode := node("alice-laptop", "100.64.0.10", "fd7a:115c:a1e0::a", users[0])
@@ -2141,8 +2140,8 @@ func TestBuildPeerMap_AutogroupInternetMakesExitNodeVisible(t *testing.T) {
// Reproduction for #3160: ambiguous user@ used to silently drop rules. // Reproduction for #3160: ambiguous user@ used to silently drop rules.
func TestNewPolicyManager_DuplicateUsername(t *testing.T) { func TestNewPolicyManager_DuplicateUsername(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 2}, Name: "yala"}, {ID: 2, Name: "yala"},
{Model: gorm.Model{ID: 7}, Name: "yala", Email: "yala@yala.yala"}, {ID: 7, Name: "yala", Email: "yala@yala.yala"},
} }
polB := []byte(`{ polB := []byte(`{
@@ -2164,7 +2163,7 @@ func TestNewPolicyManager_DuplicateUsername(t *testing.T) {
// Missing-user tokens stay tolerant per #2863; only multi-match blocks load. // Missing-user tokens stay tolerant per #2863; only multi-match blocks load.
func TestNewPolicyManager_UnknownUsernameTolerant(t *testing.T) { func TestNewPolicyManager_UnknownUsernameTolerant(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "alice"}, {ID: 1, Name: "alice"},
} }
polB := []byte(`{ polB := []byte(`{
@@ -2178,8 +2177,8 @@ func TestNewPolicyManager_UnknownUsernameTolerant(t *testing.T) {
// Rejected SetPolicy must keep the previous policy intact. // Rejected SetPolicy must keep the previous policy intact.
func TestSetPolicy_DuplicateUsername(t *testing.T) { func TestSetPolicy_DuplicateUsername(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 2}, Name: "yala"}, {ID: 2, Name: "yala"},
{Model: gorm.Model{ID: 7}, Name: "yala", Email: "yala@yala.yala"}, {ID: 7, Name: "yala", Email: "yala@yala.yala"},
} }
good := []byte(`{ good := []byte(`{
@@ -2223,9 +2222,9 @@ func TestValidateUserReferences_EmptyUsersTolerant(t *testing.T) {
// One case per AST site so a dropped walk fails the matching subtest. // One case per AST site so a dropped walk fails the matching subtest.
func TestValidateUserReferences_AllSites(t *testing.T) { func TestValidateUserReferences_AllSites(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "alice"}, {ID: 1, Name: "alice"},
{Model: gorm.Model{ID: 2}, Name: "dup"}, {ID: 2, Name: "dup"},
{Model: gorm.Model{ID: 3}, Name: "dup"}, {ID: 3, Name: "dup"},
} }
tests := []struct { tests := []struct {
@@ -2317,8 +2316,8 @@ func TestValidateUserReferences_AllSites(t *testing.T) {
// IP-level grant. // IP-level grant.
func TestPeerRelayGrantMakesRelayVisible(t *testing.T) { func TestPeerRelayGrantMakesRelayVisible(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "alice", Email: "alice@headscale.net"}, {ID: 1, Name: "alice", Email: "alice@headscale.net"},
{Model: gorm.Model{ID: 2}, Name: "tagowner", Email: "tagowner@headscale.net"}, {ID: 2, Name: "tagowner", Email: "tagowner@headscale.net"},
} }
// Helper for tagged nodes belonging to the tag-owner user. // Helper for tagged nodes belonging to the tag-owner user.
@@ -28,7 +28,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/types/testcapture" "github.com/juanfont/headscale/hscontrol/types/testcapture"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@@ -44,9 +43,9 @@ var knownPolicyTesterDivergences = map[string]string{} //nolint:gosec // strings
// minimal one is enough to make the runner go. // minimal one is enough to make the runner go.
func policyTesterCompatUsers() types.Users { func policyTesterCompatUsers() types.Users {
return types.Users{ return types.Users{
{Model: gorm.Model{ID: 1}, Name: "odin", Email: "odin@example.com"}, {ID: 1, Name: "odin", Email: "odin@example.com"},
{Model: gorm.Model{ID: 2}, Name: "thor", Email: "thor@example.org"}, {ID: 2, Name: "thor", Email: "thor@example.org"},
{Model: gorm.Model{ID: 3}, Name: "freya", Email: "freya@example.com"}, {ID: 3, Name: "freya", Email: "freya@example.com"},
} }
} }
+3 -4
View File
@@ -8,7 +8,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
) )
// sshTestUsers/sshTestNodes are reused across the table below to keep // sshTestUsers/sshTestNodes are reused across the table below to keep
@@ -22,9 +21,9 @@ import (
// - prod (alice-created tagged node) → tag:prod // - prod (alice-created tagged node) → tag:prod
func sshTestUsers() types.Users { func sshTestUsers() types.Users {
return types.Users{ return types.Users{
{Model: gorm.Model{ID: 1}, Name: "alice", Email: "alice@headscale.net"}, {ID: 1, Name: "alice", Email: "alice@headscale.net"},
{Model: gorm.Model{ID: 2}, Name: "bob", Email: "bob@headscale.net"}, {ID: 2, Name: "bob", Email: "bob@headscale.net"},
{Model: gorm.Model{ID: 3}, Name: "thor", Email: "thor@example.org"}, {ID: 3, Name: "thor", Email: "thor@example.org"},
} }
} }
@@ -28,7 +28,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/types/testcapture" "github.com/juanfont/headscale/hscontrol/types/testcapture"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@@ -45,9 +44,9 @@ func ptrAddr(s string) *netip.Addr {
// norse-god names and nodes get original-151 pokémon names. // norse-god names and nodes get original-151 pokémon names.
func setupACLCompatUsers() types.Users { func setupACLCompatUsers() types.Users {
return types.Users{ return types.Users{
{Model: gorm.Model{ID: 1}, Name: "odin", Email: "odin@example.com"}, {ID: 1, Name: "odin", Email: "odin@example.com"},
{Model: gorm.Model{ID: 2}, Name: "thor", Email: "thor@example.org"}, {ID: 2, Name: "thor", Email: "thor@example.org"},
{Model: gorm.Model{ID: 3}, Name: "freya", Email: "freya@example.com"}, {ID: 3, Name: "freya", Email: "freya@example.com"},
} }
} }
@@ -29,7 +29,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/types/testcapture" "github.com/juanfont/headscale/hscontrol/types/testcapture"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@@ -39,9 +38,9 @@ import (
// . // .
func setupGrantsCompatUsers() types.Users { func setupGrantsCompatUsers() types.Users {
return types.Users{ return types.Users{
{Model: gorm.Model{ID: 1}, Name: "odin", Email: "odin@example.com"}, {ID: 1, Name: "odin", Email: "odin@example.com"},
{Model: gorm.Model{ID: 2}, Name: "thor", Email: "thor@example.org"}, {ID: 2, Name: "thor", Email: "thor@example.org"},
{Model: gorm.Model{ID: 3}, Name: "freya", Email: "freya@example.com"}, {ID: 3, Name: "freya", Email: "freya@example.com"},
} }
} }
@@ -27,7 +27,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/types/testcapture" "github.com/juanfont/headscale/hscontrol/types/testcapture"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/views" "tailscale.com/types/views"
) )
@@ -36,9 +35,9 @@ import (
// tool's anonymizer rewrites the SaaS users into. // tool's anonymizer rewrites the SaaS users into.
func nodeAttrsCompatUsers() types.Users { func nodeAttrsCompatUsers() types.Users {
return types.Users{ return types.Users{
{Model: gorm.Model{ID: 1}, Name: "odin", Email: "odin@example.com"}, {ID: 1, Name: "odin", Email: "odin@example.com"},
{Model: gorm.Model{ID: 2}, Name: "thor", Email: "thor@example.org"}, {ID: 2, Name: "thor", Email: "thor@example.org"},
{Model: gorm.Model{ID: 3}, Name: "freya", Email: "freya@example.com"}, {ID: 3, Name: "freya", Email: "freya@example.com"},
} }
} }
@@ -42,7 +42,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go4.org/netipx" "go4.org/netipx"
"gorm.io/gorm"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@@ -81,16 +80,16 @@ func buildRoutesUsersAndNodes(
users = make(types.Users, 0, len(topo.Users)) users = make(types.Users, 0, len(topo.Users))
for _, u := range topo.Users { for _, u := range topo.Users {
users = append(users, types.User{ users = append(users, types.User{
Model: gorm.Model{ID: u.ID}, ID: u.ID,
Name: u.Name, Name: u.Name,
Email: convertSaaSEmail(u.Email), Email: convertSaaSEmail(u.Email),
}) })
} }
} else { } else {
users = types.Users{ users = types.Users{
{Model: gorm.Model{ID: 1}, Name: "kratail2tid", Email: "kratail2tid@example.com"}, {ID: 1, Name: "kratail2tid", Email: "kratail2tid@example.com"},
{Model: gorm.Model{ID: 2}, Name: "kristoffer", Email: "kristoffer@example.com"}, {ID: 2, Name: "kristoffer", Email: "kristoffer@example.com"},
{Model: gorm.Model{ID: 3}, Name: "monitorpasskeykradalby", Email: "monitorpasskeykradalby@example.com"}, {ID: 3, Name: "monitorpasskeykradalby", Email: "monitorpasskeykradalby@example.com"},
} }
} }
@@ -1455,7 +1454,7 @@ func TestRoutesCompatPeerAllowedIPs(t *testing.T) {
for _, nmPeer := range capture.Netmap.Peers { for _, nmPeer := range capture.Netmap.Peers {
// Extract the short name from the FQDN. // Extract the short name from the FQDN.
peerName := strings.Split(nmPeer.Name(), ".")[0] peerName, _, _ := strings.Cut(nmPeer.Name(), ".")
peer := findNodeByGivenName(nodes, peerName) peer := findNodeByGivenName(nodes, peerName)
if peer == nil { if peer == nil {
@@ -18,7 +18,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types/testcapture" "github.com/juanfont/headscale/hscontrol/types/testcapture"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@@ -29,17 +28,17 @@ import (
func setupSSHDataCompatUsers() types.Users { func setupSSHDataCompatUsers() types.Users {
return types.Users{ return types.Users{
{ {
Model: gorm.Model{ID: 1}, ID: 1,
Name: "odin", Name: "odin",
Email: "odin@example.com", Email: "odin@example.com",
}, },
{ {
Model: gorm.Model{ID: 2}, ID: 2,
Name: "thor", Name: "thor",
Email: "thor@example.org", Email: "thor@example.org",
}, },
{ {
Model: gorm.Model{ID: 3}, ID: 3,
Name: "freya", Name: "freya",
Email: "freya@example.com", Email: "freya@example.com",
}, },
+3 -4
View File
@@ -7,15 +7,14 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gorm.io/gorm"
) )
// policyTestUsers/policyTestNodes are reused across the test cases below to // policyTestUsers/policyTestNodes are reused across the test cases below to
// keep each table row focussed on the policy + tests under exercise. // keep each table row focussed on the policy + tests under exercise.
func policyTestUsers() types.Users { func policyTestUsers() types.Users {
return types.Users{ return types.Users{
{Model: gorm.Model{ID: 1}, Name: "alice", Email: "alice@headscale.net"}, {ID: 1, Name: "alice", Email: "alice@headscale.net"},
{Model: gorm.Model{ID: 2}, Name: "bob", Email: "bob@headscale.net"}, {ID: 2, Name: "bob", Email: "bob@headscale.net"},
} }
} }
@@ -351,7 +350,7 @@ func TestNewPolicyManagerSkipsTests(t *testing.T) {
// a `ip: tcp:80` grant). // a `ip: tcp:80` grant).
func TestRunTestsEmptyProtoMatchesDefaultProtocols(t *testing.T) { func TestRunTestsEmptyProtoMatchesDefaultProtocols(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "odin", Email: "odin@example.com"}, {ID: 1, Name: "odin", Email: "odin@example.com"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
{ {
+15 -15
View File
@@ -2808,9 +2808,9 @@ func TestResolvePolicy(t *testing.T) {
func TestResolveAutoApprovers(t *testing.T) { func TestResolveAutoApprovers(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
{Model: gorm.Model{ID: 3}, Name: "user3"}, {ID: 3, Name: "user3"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -3274,9 +3274,9 @@ func ipSetComparer(x, y *netipx.IPSet) bool {
func TestNodeCanApproveRoute(t *testing.T) { func TestNodeCanApproveRoute(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
{Model: gorm.Model{ID: 3}, Name: "user3"}, {ID: 3, Name: "user3"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -3407,9 +3407,9 @@ func TestNodeCanApproveRoute(t *testing.T) {
func TestResolveTagOwners(t *testing.T) { func TestResolveTagOwners(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
{Model: gorm.Model{ID: 3}, Name: "user3"}, {ID: 3, Name: "user3"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -3507,9 +3507,9 @@ func TestResolveTagOwners(t *testing.T) {
func TestNodeCanHaveTag(t *testing.T) { func TestNodeCanHaveTag(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
{Model: gorm.Model{ID: 3}, Name: "user3"}, {ID: 3, Name: "user3"},
} }
nodes := types.Nodes{ nodes := types.Nodes{
@@ -3786,9 +3786,9 @@ func TestNodeCanHaveTag(t *testing.T) {
func TestUserMatchesOwner(t *testing.T) { func TestUserMatchesOwner(t *testing.T) {
users := types.Users{ users := types.Users{
{Model: gorm.Model{ID: 1}, Name: "user1"}, {ID: 1, Name: "user1"},
{Model: gorm.Model{ID: 2}, Name: "user2"}, {ID: 2, Name: "user2"},
{Model: gorm.Model{ID: 3}, Name: "user3"}, {ID: 3, Name: "user3"},
} }
tests := []struct { tests := []struct {
+1 -2
View File
@@ -5,7 +5,6 @@ import (
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gorm.io/gorm"
) )
// TestNodeIsTagged tests the [Node.IsTagged] method for determining if a node is tagged. // TestNodeIsTagged tests the [Node.IsTagged] method for determining if a node is tagged.
@@ -285,7 +284,7 @@ func TestNodeOwnershipModel(t *testing.T) {
// TestUserTypedID tests the TypedID() helper method. // TestUserTypedID tests the TypedID() helper method.
func TestUserTypedID(t *testing.T) { func TestUserTypedID(t *testing.T) {
user := User{ user := User{
Model: gorm.Model{ID: 42}, ID: 42,
} }
typedID := user.TypedID() typedID := user.TypedID()
+1 -1
View File
@@ -40,7 +40,7 @@ const (
// Tagged nodes don't belong to a real user - the tag is their identity. // Tagged nodes don't belong to a real user - the tag is their identity.
// This special user ID is used when rendering tagged nodes in the Tailscale protocol. // This special user ID is used when rendering tagged nodes in the Tailscale protocol.
var TaggedDevices = User{ var TaggedDevices = User{
Model: gorm.Model{ID: TaggedDevicesUserID}, ID: TaggedDevicesUserID,
Name: "tagged-devices", Name: "tagged-devices",
DisplayName: "Tagged Devices", DisplayName: "Tagged Devices",
} }
+1 -1
View File
@@ -324,7 +324,7 @@ func TestACLHostsInNetMapTable(t *testing.T) {
user := status.User[status.Self.UserID].LoginName user := status.User[status.Self.UserID].LoginName
assert.Len(c, status.Peer, (testCase.want[user])) assert.Len(c, status.Peer, testCase.want[user])
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for expected peer visibility") }, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for expected peer visibility")
} }
}) })