mirror of
https://github.com/juanfont/headscale.git
synced 2026-09-29 19:39:37 +09:00
db: name the nodes that block a user deletion
The error only said the user still has nodes, which the CLI prompt did not mention at all. Wrap ErrUserStillHasNodes with the ID and hostname of every blocking node so the operator knows what to remove. Run the DestroyUser test table on Postgres as well as SQLite, since the two schemas define different foreign-key actions; the Postgres variant skips without a local server.
This commit is contained in:
committed by
Kristoffer Dalby
parent
d60bac5c79
commit
04d1e3c83f
@@ -54,6 +54,7 @@ tags; any other tag is rejected, for new and re-registering nodes alike. See
|
||||
- Expiring or deleting a non-existent pre-auth key now returns an error instead of silently succeeding [#3324](https://github.com/juanfont/headscale/pull/3324)
|
||||
- Improve systemd service file hardening [#3341](https://github.com/juanfont/headscale/pull/3341)
|
||||
- Fix `headscale users destroy`/`rename` reporting "multiple users match query" when no user matches; an ambiguous match now lists the matching users [#3476](https://github.com/juanfont/headscale/pull/3476)
|
||||
- Deleting a user that still owns nodes now lists the nodes (ID and hostname) that must be deleted first [#3475](https://github.com/juanfont/headscale/pull/3475)
|
||||
- Headscale now requires Go 1.27 to build
|
||||
|
||||
## 0.29.4 (2026-09-23)
|
||||
|
||||
+10
-1
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
@@ -61,7 +62,15 @@ func DestroyUser(tx *gorm.DB, uid types.UserID) error {
|
||||
}
|
||||
|
||||
if len(nodes) > 0 {
|
||||
return ErrUserStillHasNodes
|
||||
blocking := make([]string, len(nodes))
|
||||
for i, node := range nodes {
|
||||
blocking[i] = fmt.Sprintf("%d (%s)", node.ID.Uint64(), node.Hostname)
|
||||
}
|
||||
|
||||
return fmt.Errorf(
|
||||
"%w: %d node(s) must be deleted first: %s",
|
||||
ErrUserStillHasNodes, len(nodes), strings.Join(blocking, ", "),
|
||||
)
|
||||
}
|
||||
|
||||
keys, err := ListPreAuthKeysByUser(tx, uid)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
@@ -86,7 +87,9 @@ func TestDestroyUserErrors(t *testing.T) {
|
||||
require.NoError(t, trx.Error)
|
||||
|
||||
err = db.DestroyUser(types.UserID(user.ID))
|
||||
assert.ErrorIs(t, err, ErrUserStillHasNodes)
|
||||
require.ErrorIs(t, err, ErrUserStillHasNodes)
|
||||
// The error names the blocking node so it can be found.
|
||||
require.ErrorContains(t, err, fmt.Sprintf("%d (testnode)", node.ID))
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -204,13 +207,20 @@ func TestDestroyUserErrors(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
// User deletion depends on foreign-key actions that differ between the
|
||||
// hand-written SQLite schema and the GORM-generated Postgres schema, so
|
||||
// run every case on both. The Postgres variant skips when no local
|
||||
// server can be started.
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Run(tt.name+"-sqlite", func(t *testing.T) {
|
||||
db, err := newSQLiteTestDB()
|
||||
require.NoError(t, err)
|
||||
|
||||
tt.test(t, db)
|
||||
})
|
||||
t.Run(tt.name+"-postgres", func(t *testing.T) {
|
||||
tt.test(t, newPostgresTestDB(t))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user