65 lines
2.0 KiB
Go
65 lines
2.0 KiB
Go
package data
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"database/sql"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"time"
|
|
|
|
configpkg "kra/internal/config"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// acquireInitializationLock serializes first-install work across backend Pods.
|
|
// The Kubernetes deployment uses MySQL, whose named lock is connection-scoped
|
|
// and therefore remains held while the candidate database is migrated and seeded.
|
|
func acquireInitializationLock(ctx context.Context, db *gorm.DB, database *configpkg.Database) (func(), error) {
|
|
if normalizedDriver(database.Driver) != "mysql" {
|
|
return func() {}, nil
|
|
}
|
|
sqlDB, err := db.DB()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open initialization lock connection: %w", err)
|
|
}
|
|
conn, err := sqlDB.Conn(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reserve initialization lock connection: %w", err)
|
|
}
|
|
lockName := initializationLockName(database)
|
|
var acquired sql.NullInt64
|
|
if err := conn.QueryRowContext(ctx, "SELECT GET_LOCK(?, ?)", lockName, 60).Scan(&acquired); err != nil {
|
|
_ = conn.Close()
|
|
return nil, fmt.Errorf("acquire database initialization lock: %w", err)
|
|
}
|
|
if !acquired.Valid || acquired.Int64 != 1 {
|
|
_ = conn.Close()
|
|
return nil, fmt.Errorf("database initialization lock is busy")
|
|
}
|
|
return func() {
|
|
releaseCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
_, _ = conn.ExecContext(releaseCtx, "SELECT RELEASE_LOCK(?)", lockName)
|
|
cancel()
|
|
_ = conn.Close()
|
|
}, nil
|
|
}
|
|
|
|
func initializationLockName(database *configpkg.Database) string {
|
|
identity := fmt.Sprintf("%s\x00%s\x00%s\x00%s", database.Driver, database.Host, database.Port, database.Name)
|
|
sum := sha256.Sum256([]byte(identity))
|
|
return "kra-init-" + hex.EncodeToString(sum[:])[:48]
|
|
}
|
|
|
|
func persistedDatabaseConfigured(runtime *configpkg.Store) bool {
|
|
if runtime == nil || runtime.ConfigPath() == "" {
|
|
return false
|
|
}
|
|
persisted, err := configpkg.Load(runtime.ConfigPath())
|
|
if err != nil || persisted == nil || persisted.Data == nil {
|
|
return false
|
|
}
|
|
return databaseConnectionConfigured(persisted.Data.Database)
|
|
}
|