restructure command/api to use stable IDs (#2261)

This commit is contained in:
Kristoffer Dalby 2024-12-10 16:23:55 +01:00 committed by GitHub
parent 08bd4b9bc5
commit 64fd1f9483
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1902 additions and 3613 deletions

View file

@ -130,22 +130,22 @@ func TestOIDCAuthenticationPingAll(t *testing.T) {
want := []v1.User{
{
Id: "1",
Id: 1,
Name: "user1",
},
{
Id: "2",
Id: 2,
Name: "user1",
Email: "user1@headscale.net",
Provider: "oidc",
ProviderId: oidcConfig.Issuer + "/user1",
},
{
Id: "3",
Id: 3,
Name: "user2",
},
{
Id: "4",
Id: 4,
Name: "user2",
Email: "", // Unverified
Provider: "oidc",
@ -260,22 +260,22 @@ func TestOIDC024UserCreation(t *testing.T) {
want: func(iss string) []v1.User {
return []v1.User{
{
Id: "1",
Id: 1,
Name: "user1",
},
{
Id: "2",
Id: 2,
Name: "user1",
Email: "user1@headscale.net",
Provider: "oidc",
ProviderId: iss + "/user1",
},
{
Id: "3",
Id: 3,
Name: "user2",
},
{
Id: "4",
Id: 4,
Name: "user2",
Email: "user2@headscale.net",
Provider: "oidc",
@ -295,21 +295,21 @@ func TestOIDC024UserCreation(t *testing.T) {
want: func(iss string) []v1.User {
return []v1.User{
{
Id: "1",
Id: 1,
Name: "user1",
},
{
Id: "2",
Id: 2,
Name: "user1",
Provider: "oidc",
ProviderId: iss + "/user1",
},
{
Id: "3",
Id: 3,
Name: "user2",
},
{
Id: "4",
Id: 4,
Name: "user2",
Provider: "oidc",
ProviderId: iss + "/user2",
@ -329,14 +329,14 @@ func TestOIDC024UserCreation(t *testing.T) {
want: func(iss string) []v1.User {
return []v1.User{
{
Id: "1",
Id: 1,
Name: "user1",
Email: "user1@headscale.net",
Provider: "oidc",
ProviderId: iss + "/user1",
},
{
Id: "2",
Id: 2,
Name: "user2",
Email: "user2@headscale.net",
Provider: "oidc",
@ -357,21 +357,21 @@ func TestOIDC024UserCreation(t *testing.T) {
want: func(iss string) []v1.User {
return []v1.User{
{
Id: "1",
Id: 1,
Name: "user1",
},
{
Id: "2",
Id: 2,
Name: "user1",
Provider: "oidc",
ProviderId: iss + "/user1",
},
{
Id: "3",
Id: 3,
Name: "user2",
},
{
Id: "4",
Id: 4,
Name: "user2",
Provider: "oidc",
ProviderId: iss + "/user2",
@ -393,14 +393,14 @@ func TestOIDC024UserCreation(t *testing.T) {
// Hmm I think we will have to overwrite the initial name here
// createuser with "user1.headscale.net", but oidc with "user1"
{
Id: "1",
Id: 1,
Name: "user1",
Email: "user1@headscale.net",
Provider: "oidc",
ProviderId: iss + "/user1",
},
{
Id: "2",
Id: 2,
Name: "user2",
Email: "user2@headscale.net",
Provider: "oidc",
@ -421,21 +421,21 @@ func TestOIDC024UserCreation(t *testing.T) {
want: func(iss string) []v1.User {
return []v1.User{
{
Id: "1",
Id: 1,
Name: "user1.headscale.net",
},
{
Id: "2",
Id: 2,
Name: "user1",
Provider: "oidc",
ProviderId: iss + "/user1",
},
{
Id: "3",
Id: 3,
Name: "user2.headscale.net",
},
{
Id: "4",
Id: 4,
Name: "user2",
Provider: "oidc",
ProviderId: iss + "/user2",

View file

@ -1,18 +1,21 @@
package integration
import (
"cmp"
"encoding/json"
"fmt"
"sort"
"strings"
"testing"
"time"
tcmp "github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/tsic"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/exp/slices"
)
@ -30,6 +33,16 @@ func executeAndUnmarshal[T any](headscale ControlServer, command []string, resul
return nil
}
// Interface ensuring that we can sort structs from gRPC that
// have an ID field.
type GRPCSortable interface {
GetId() uint64
}
func sortWithID[T GRPCSortable](a, b T) int {
return cmp.Compare(a.GetId(), b.GetId())
}
func TestUserCommand(t *testing.T) {
IntegrationSkip(t)
t.Parallel()
@ -49,7 +62,7 @@ func TestUserCommand(t *testing.T) {
headscale, err := scenario.Headscale()
assertNoErr(t, err)
var listUsers []v1.User
var listUsers []*v1.User
err = executeAndUnmarshal(headscale,
[]string{
"headscale",
@ -62,8 +75,8 @@ func TestUserCommand(t *testing.T) {
)
assertNoErr(t, err)
slices.SortFunc(listUsers, sortWithID)
result := []string{listUsers[0].GetName(), listUsers[1].GetName()}
sort.Strings(result)
assert.Equal(
t,
@ -76,15 +89,14 @@ func TestUserCommand(t *testing.T) {
"headscale",
"users",
"rename",
"--output",
"json",
"user2",
"newname",
"--output=json",
fmt.Sprintf("--identifier=%d", listUsers[1].GetId()),
"--new-name=newname",
},
)
assertNoErr(t, err)
var listAfterRenameUsers []v1.User
var listAfterRenameUsers []*v1.User
err = executeAndUnmarshal(headscale,
[]string{
"headscale",
@ -97,14 +109,131 @@ func TestUserCommand(t *testing.T) {
)
assertNoErr(t, err)
slices.SortFunc(listUsers, sortWithID)
result = []string{listAfterRenameUsers[0].GetName(), listAfterRenameUsers[1].GetName()}
sort.Strings(result)
assert.Equal(
t,
[]string{"newname", "user1"},
[]string{"user1", "newname"},
result,
)
var listByUsername []*v1.User
err = executeAndUnmarshal(headscale,
[]string{
"headscale",
"users",
"list",
"--output",
"json",
"--name=user1",
},
&listByUsername,
)
assertNoErr(t, err)
slices.SortFunc(listByUsername, sortWithID)
want := []*v1.User{
{
Id: 1,
Name: "user1",
},
}
if diff := tcmp.Diff(want, listByUsername, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
t.Errorf("unexpected users (-want +got):\n%s", diff)
}
var listByID []*v1.User
err = executeAndUnmarshal(headscale,
[]string{
"headscale",
"users",
"list",
"--output",
"json",
"--identifier=1",
},
&listByID,
)
assertNoErr(t, err)
slices.SortFunc(listByID, sortWithID)
want = []*v1.User{
{
Id: 1,
Name: "user1",
},
}
if diff := tcmp.Diff(want, listByID, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
t.Errorf("unexpected users (-want +got):\n%s", diff)
}
deleteResult, err := headscale.Execute(
[]string{
"headscale",
"users",
"destroy",
"--force",
// Delete "user1"
"--identifier=1",
},
)
assert.Nil(t, err)
assert.Contains(t, deleteResult, "User destroyed")
var listAfterIDDelete []*v1.User
err = executeAndUnmarshal(headscale,
[]string{
"headscale",
"users",
"list",
"--output",
"json",
},
&listAfterIDDelete,
)
assertNoErr(t, err)
slices.SortFunc(listAfterIDDelete, sortWithID)
want = []*v1.User{
{
Id: 2,
Name: "newname",
},
}
if diff := tcmp.Diff(want, listAfterIDDelete, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
t.Errorf("unexpected users (-want +got):\n%s", diff)
}
deleteResult, err = headscale.Execute(
[]string{
"headscale",
"users",
"destroy",
"--force",
"--name=newname",
},
)
assert.Nil(t, err)
assert.Contains(t, deleteResult, "User destroyed")
var listAfterNameDelete []v1.User
err = executeAndUnmarshal(headscale,
[]string{
"headscale",
"users",
"list",
"--output",
"json",
},
&listAfterNameDelete,
)
assertNoErr(t, err)
require.Len(t, listAfterNameDelete, 0)
}
func TestPreAuthKeyCommand(t *testing.T) {
@ -1716,4 +1845,3 @@ func TestPolicyBrokenConfigCommand(t *testing.T) {
)
assert.ErrorContains(t, err, "acl policy not found")
}

View file

@ -32,7 +32,7 @@ import (
const (
hsicHashLength = 6
dockerContextPath = "../."
caCertRoot = "/usr/local/share/ca-certificates"
caCertRoot = "/usr/local/share/ca-certificates"
aclPolicyPath = "/etc/headscale/acl.hujson"
tlsCertPath = "/etc/headscale/tls.cert"
tlsKeyPath = "/etc/headscale/tls.key"
@ -617,6 +617,7 @@ func (t *HeadscaleInContainer) Execute(
[]string{},
)
if err != nil {
log.Printf("command: %v", command)
log.Printf("command stderr: %s\n", stderr)
if stdout != "" {

View file

@ -22,6 +22,7 @@ import (
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
xmaps "golang.org/x/exp/maps"
"golang.org/x/sync/errgroup"
"tailscale.com/envknob"
)
@ -512,23 +513,26 @@ func (s *Scenario) CreateHeadscaleEnv(
return err
}
for userName, clientCount := range users {
err = s.CreateUser(userName)
usernames := xmaps.Keys(users)
sort.Strings(usernames)
for _, username := range usernames {
clientCount := users[username]
err = s.CreateUser(username)
if err != nil {
return err
}
err = s.CreateTailscaleNodesInUser(userName, "all", clientCount, tsOpts...)
err = s.CreateTailscaleNodesInUser(username, "all", clientCount, tsOpts...)
if err != nil {
return err
}
key, err := s.CreatePreAuthKey(userName, true, false)
key, err := s.CreatePreAuthKey(username, true, false)
if err != nil {
return err
}
err = s.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey())
err = s.RunTailscaleUp(username, headscale.GetEndpoint(), key.GetKey())
if err != nil {
return err
}