fix🐛: embed the seed SQL so migrate works outside the repository root

The seed scripts were read from ./config/ only, so migrating an empty database
failed anywhere that directory was absent, such as a container holding just
the binary and its settings. Embed them and prefer a file on disk when there
is one.
This commit is contained in:
zhangwenjian
2026-10-07 17:32:42 +08:00
parent ea679bf00b
commit 3a96f37391
4 changed files with 68 additions and 4 deletions
+2 -2
View File
@@ -3,8 +3,8 @@ package models
import (
"fmt"
"go-admin/common/global"
"go-admin/config"
"gorm.io/gorm"
"io/ioutil"
"log"
"strings"
)
@@ -44,7 +44,7 @@ func ExecSql(db *gorm.DB, filePath string) error {
}
func Ioutil(filePath string) (string, error) {
if contents, err := ioutil.ReadFile(filePath); err == nil {
if contents, err := config.ReadSeed(filePath); err == nil {
//因为contents是[]byte类型,直接转换成string类型后会多一行空格,需要使用strings.Replace替换换行符
result := strings.Replace(string(contents), "\n", "", 1)
fmt.Println("Use ioutil.ReadFile to read a file:", result)
+2 -2
View File
@@ -3,7 +3,7 @@ package models
import (
"fmt"
"go-admin/common/global"
"io/ioutil"
"go-admin/config"
"log"
"strings"
@@ -61,7 +61,7 @@ func ExecSql(db *gorm.DB, filePath string) error {
}
func Ioutil(filePath string) (string, error) {
if contents, err := ioutil.ReadFile(filePath); err == nil {
if contents, err := config.ReadSeed(filePath); err == nil {
//因为contents是[]byte类型,直接转换成string类型后会多一行空格,需要使用strings.Replace替换换行符
result := strings.Replace(string(contents), "\n", "", 1)
fmt.Println("Use ioutil.ReadFile to read a file:", result)
+31
View File
@@ -0,0 +1,31 @@
package config
import (
"embed"
"errors"
"io/fs"
"os"
"path"
)
// The seed scripts ship inside the binary so a database can be created from
// wherever the binary runs. They used to be read from ./config/ only, which
// fails for anything not started from the repository root - a Docker image
// that carries just the binary and its settings file, for one.
//
//go:embed db.sql pg.sql db-begin-mysql.sql db-end-mysql.sql
var seedFS embed.FS
// ReadSeed returns the seed script at name (e.g. "config/db.sql").
// A file on disk wins, so a deployment that edits its own copy keeps doing
// so; the embedded copy is the fallback when there is none.
func ReadSeed(name string) ([]byte, error) {
b, err := os.ReadFile(name)
if err == nil || !errors.Is(err, fs.ErrNotExist) {
return b, err
}
if emb, embErr := seedFS.ReadFile(path.Base(name)); embErr == nil {
return emb, nil
}
return nil, err
}
+33
View File
@@ -0,0 +1,33 @@
package config
import (
"os"
"path/filepath"
"testing"
)
func TestReadSeedFallsBackToEmbedded(t *testing.T) {
// A directory with no config/ in it, as in a container.
missing := filepath.Join(t.TempDir(), "config", "db.sql")
b, err := ReadSeed(missing)
if err != nil || len(b) == 0 {
t.Fatalf("embedded fallback: len=%d err=%v", len(b), err)
}
}
func TestReadSeedPrefersDisk(t *testing.T) {
p := filepath.Join(t.TempDir(), "db.sql")
if err := os.WriteFile(p, []byte("-- mine;"), 0o600); err != nil {
t.Fatal(err)
}
b, err := ReadSeed(p)
if err != nil || string(b) != "-- mine;" {
t.Fatalf("got %q, %v", b, err)
}
}
func TestReadSeedUnknownFileStillErrors(t *testing.T) {
if _, err := ReadSeed(filepath.Join(t.TempDir(), "nope.sql")); err == nil {
t.Fatal("expected an error for a script that is not embedded")
}
}