HH-442: isolate runtime processes and harden shutdown (#90)
* HH-442: isolate runtime processes and harden shutdown * HH-442: harden worker shutdown races * HH-442: gate dependency shutdown on active handlers --------- Co-authored-by: Rogee <rogee@ipao.vip>
This commit is contained in:
@@ -1,15 +1,19 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var unsupportedPQEnvironmentKeys = []string{"PGSERVICE", "PGSERVICEFILE", "PGREALM"}
|
||||
@@ -164,6 +168,54 @@ func CurrentVersion(dbURL string, migrationsPath string) (uint, bool, error) {
|
||||
return version, dirty, nil
|
||||
}
|
||||
|
||||
// LatestVersion returns the highest numbered up migration on disk.
|
||||
func LatestVersion(migrationsPath string) (uint, error) {
|
||||
entries, err := os.ReadDir(migrationsPath)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("read migrations: %w", err)
|
||||
}
|
||||
var latest uint64
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if entry.IsDir() || !strings.HasSuffix(name, ".up.sql") {
|
||||
continue
|
||||
}
|
||||
prefix := strings.SplitN(filepath.Base(name), "_", 2)[0]
|
||||
version, parseErr := strconv.ParseUint(prefix, 10, 64)
|
||||
if parseErr != nil {
|
||||
continue
|
||||
}
|
||||
if version > latest {
|
||||
latest = version
|
||||
}
|
||||
}
|
||||
if latest == 0 {
|
||||
return 0, errors.New("no numbered up migrations found")
|
||||
}
|
||||
return uint(latest), nil
|
||||
}
|
||||
|
||||
// CheckVersion verifies that golang-migrate reached the expected clean version.
|
||||
func CheckVersion(ctx context.Context, db *gorm.DB, expected uint) error {
|
||||
if db == nil {
|
||||
return errors.New("database is not configured")
|
||||
}
|
||||
var state struct {
|
||||
Version uint
|
||||
Dirty bool
|
||||
}
|
||||
if err := db.WithContext(ctx).Table("schema_migrations").Select("version, dirty").Take(&state).Error; err != nil {
|
||||
return fmt.Errorf("read schema migration state: %w", err)
|
||||
}
|
||||
if state.Dirty {
|
||||
return fmt.Errorf("schema migration %d is dirty", state.Version)
|
||||
}
|
||||
if state.Version != expected {
|
||||
return fmt.Errorf("schema migration version %d, expected %d", state.Version, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMigrate(dbURL, migrationsPath string) (*migrate.Migrate, error) {
|
||||
if strings.TrimSpace(migrationsPath) == "" {
|
||||
return nil, errors.New("migration path must not be empty")
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestMigrationCommandsRejectBlankPath(t *testing.T) {
|
||||
@@ -56,3 +60,23 @@ func TestSanitizePostgresEnvironment(t *testing.T) {
|
||||
require.False(t, exists, "%s should be removed at application startup", key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLatestVersionAndCheckVersion(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "000001_first.up.sql"), []byte("SELECT 1;"), 0o600))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "000012_latest.up.sql"), []byte("SELECT 1;"), 0o600))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "README"), nil, 0o600))
|
||||
|
||||
latest, err := LatestVersion(dir)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint(12), latest)
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.Exec("CREATE TABLE schema_migrations (version INTEGER NOT NULL, dirty BOOLEAN NOT NULL)").Error)
|
||||
require.NoError(t, db.Exec("INSERT INTO schema_migrations(version, dirty) VALUES (12, false)").Error)
|
||||
require.NoError(t, CheckVersion(context.Background(), db, latest))
|
||||
require.Error(t, CheckVersion(context.Background(), db, 13))
|
||||
require.NoError(t, db.Exec("UPDATE schema_migrations SET dirty = true").Error)
|
||||
require.ErrorContains(t, CheckVersion(context.Background(), db, latest), "dirty")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user