From 3a96f37391e8f74cccba0757cdc90cd74575ac5b Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Wed, 7 Oct 2026 17:32:42 +0800 Subject: [PATCH] =?UTF-8?q?fix=F0=9F=90=9B:=20embed=20the=20seed=20SQL=20s?= =?UTF-8?q?o=20migrate=20works=20outside=20the=20repository=20root?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- app/admin/models/initdb.go | 4 ++-- cmd/migrate/migration/models/initdb.go | 4 ++-- config/seed.go | 31 ++++++++++++++++++++++++ config/seed_test.go | 33 ++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 config/seed.go create mode 100644 config/seed_test.go diff --git a/app/admin/models/initdb.go b/app/admin/models/initdb.go index cdec260f..ed8fcbfe 100644 --- a/app/admin/models/initdb.go +++ b/app/admin/models/initdb.go @@ -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) diff --git a/cmd/migrate/migration/models/initdb.go b/cmd/migrate/migration/models/initdb.go index ba7f4ca7..61557418 100644 --- a/cmd/migrate/migration/models/initdb.go +++ b/cmd/migrate/migration/models/initdb.go @@ -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) diff --git a/config/seed.go b/config/seed.go new file mode 100644 index 00000000..ae2e5f19 --- /dev/null +++ b/config/seed.go @@ -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 +} diff --git a/config/seed_test.go b/config/seed_test.go new file mode 100644 index 00000000..4040f1f5 --- /dev/null +++ b/config/seed_test.go @@ -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") + } +}