Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix postgres migration issue with 0.24 #2367

Merged
merged 3 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

### Changes

- Fix migration issue with user table for PostgreSQL
[#2367](https://github.com/juanfont/headscale/pull/2367)
- Relax username validation to allow emails
[#2364](https://github.com/juanfont/headscale/pull/2364)

Expand Down
32 changes: 32 additions & 0 deletions hscontrol/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,38 @@ func NewHeadscaleDatabase(
// populate the user with more interesting information.
ID: "202407191627",
Migrate: func(tx *gorm.DB) error {
// Fix an issue where the automigration in GORM expected a constraint to
// exists that didnt, and add the one it wanted.
// Fixes https://github.com/juanfont/headscale/issues/2351
if cfg.Type == types.DatabasePostgres {
err := tx.Exec(`
BEGIN;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uni_users_name'
) THEN
ALTER TABLE users ADD CONSTRAINT uni_users_name UNIQUE (name);
END IF;
END $$;

DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'users_name_key'
) THEN
ALTER TABLE users DROP CONSTRAINT users_name_key;
END IF;
END $$;
COMMIT;
`).Error
if err != nil {
return fmt.Errorf("failed to rename constraint: %w", err)
}
}

err := tx.AutoMigrate(&types.User{})
if err != nil {
return err
Expand Down
61 changes: 60 additions & 1 deletion hscontrol/db/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"net/netip"
"os"
"os/exec"
"path/filepath"
"slices"
"sort"
Expand All @@ -23,7 +24,10 @@ import (
"zgo.at/zcache/v2"
)

func TestMigrations(t *testing.T) {
// TestMigrationsSQLite is the main function for testing migrations,
// we focus on SQLite correctness as it is the main database used in headscale.
// All migrations that are worth testing should be added here.
func TestMigrationsSQLite(t *testing.T) {
ipp := func(p string) netip.Prefix {
return netip.MustParsePrefix(p)
}
Expand Down Expand Up @@ -375,3 +379,58 @@ func TestConstraints(t *testing.T) {
})
}
}

func TestMigrationsPostgres(t *testing.T) {
tests := []struct {
name string
dbPath string
wantFunc func(*testing.T, *HSDatabase)
}{
{
name: "user-idx-breaking",
dbPath: "testdata/pre-24-postgresdb.pssql.dump",
wantFunc: func(t *testing.T, h *HSDatabase) {
users, err := Read(h.DB, func(rx *gorm.DB) ([]types.User, error) {
return ListUsers(rx)
})
require.NoError(t, err)

for _, user := range users {
assert.NotEmpty(t, user.Name)
assert.Empty(t, user.ProfilePicURL)
assert.Empty(t, user.Email)
}
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u := newPostgresDBForTest(t)

pgRestorePath, err := exec.LookPath("pg_restore")
if err != nil {
t.Fatal("pg_restore not found in PATH. Please install it and ensure it is accessible.")
}

// Construct the pg_restore command
cmd := exec.Command(pgRestorePath, "--verbose", "--if-exists", "--clean", "--no-owner", "--dbname", u.String(), tt.dbPath)

// Set the output streams
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

// Execute the command
err = cmd.Run()
if err != nil {
t.Fatalf("failed to restore postgres database: %s", err)
}

db = newHeadscaleDBFromPostgresURL(t, u)

if tt.wantFunc != nil {
tt.wantFunc(t, db)
}
})
}
}
18 changes: 11 additions & 7 deletions hscontrol/db/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,11 @@ func newSQLiteTestDB() (*HSDatabase, error) {
func newPostgresTestDB(t *testing.T) *HSDatabase {
t.Helper()

var err error
tmpDir, err = os.MkdirTemp("", "headscale-db-test-*")
if err != nil {
t.Fatal(err)
}
return newHeadscaleDBFromPostgresURL(t, newPostgresDBForTest(t))
}

log.Printf("database path: %s", tmpDir+"/headscale_test.db")
func newPostgresDBForTest(t *testing.T) *url.URL {
t.Helper()

ctx := context.Background()
srv, err := postgrestest.Start(ctx)
Expand All @@ -100,10 +98,16 @@ func newPostgresTestDB(t *testing.T) *HSDatabase {
t.Logf("created local postgres: %s", u)
pu, _ := url.Parse(u)

return pu
}

func newHeadscaleDBFromPostgresURL(t *testing.T, pu *url.URL) *HSDatabase {
t.Helper()

pass, _ := pu.User.Password()
port, _ := strconv.Atoi(pu.Port())

db, err = NewHeadscaleDatabase(
db, err := NewHeadscaleDatabase(
types.DatabaseConfig{
Type: types.DatabasePostgres,
Postgres: types.PostgresConfig{
Expand Down
Binary file not shown.
Loading