X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/41b9b83812826b77034fe13ea34047e194b1027f..064df2d66faf61f475813863e5c29ca07ad9555a:/lib/boot/rails_db.go diff --git a/lib/boot/rails_db.go b/lib/boot/rails_db.go index 3f81511445..16e150172d 100644 --- a/lib/boot/rails_db.go +++ b/lib/boot/rails_db.go @@ -13,6 +13,7 @@ import ( "git.arvados.org/arvados.git/lib/controller/dblock" "git.arvados.org/arvados.git/lib/ctrlctx" + "github.com/sirupsen/logrus" ) type railsDatabase struct{} @@ -21,6 +22,10 @@ func (runner railsDatabase) String() string { return "railsDatabase" } +// Run checks for and applies any pending Rails database migrations. +// +// If running a dev/test environment, and the database is empty, it +// initializes the database. func (runner railsDatabase) Run(ctx context.Context, fail func(error), super *Supervisor) error { err := super.wait(ctx, runPostgreSQL{}, installPassenger{src: "services/api"}) if err != nil { @@ -35,14 +40,18 @@ func (runner railsDatabase) Run(ctx context.Context, fail func(error), super *Su appdir = filepath.Join(super.SourcePath, "services/api") } - // list versions in db/migrate/{version}_{name}.rb - todo := map[string]bool{} - fs.WalkDir(os.DirFS(appdir), "db/migrate", func(path string, d fs.DirEntry, err error) error { - if cut := strings.Index(d.Name(), "_"); cut > 0 && strings.HasSuffix(d.Name(), ".rb") { - todo[d.Name()[:cut]] = true - } - return nil - }) + // Check for pending migrations before running rake. + // + // In principle, we could use "rake db:migrate:status" or skip + // this check entirely and let "rake db:migrate" be a no-op + // most of the time. However, in the most common case when + // there are no new migrations, that would add ~2s to startup + // time / downtime during service restart. + + todo, err := migrationList(appdir, super.logger) + if err != nil { + return err + } // read schema_migrations table (list of migrations already // applied) and remove those entries from todo @@ -87,3 +96,37 @@ func (runner railsDatabase) Run(ctx context.Context, fail func(error), super *Su defer dblock.RailsMigrations.Unlock() return super.RunProgram(ctx, appdir, runOptions{env: railsEnv}, "bundle", "exec", "rake", "db:migrate") } + +func migrationList(dir string, log logrus.FieldLogger) (map[string]bool, error) { + todo := map[string]bool{} + + // list versions in db/migrate/{version}_{name}.rb + err := fs.WalkDir(os.DirFS(dir), "db/migrate", func(path string, d fs.DirEntry, err error) error { + if d.IsDir() { + return nil + } + fnm := d.Name() + if !strings.HasSuffix(fnm, ".rb") { + log.Warnf("unexpected file in db/migrate dir: %s", fnm) + return nil + } + for i, c := range fnm { + if i > 0 && c == '_' { + todo[fnm[:i]] = true + break + } + if c < '0' || c > '9' { + // non-numeric character before the + // first '_' means this is not a + // migration + log.Warnf("unexpected file in db/migrate dir: %s", fnm) + return nil + } + } + return nil + }) + if err != nil { + return nil, err + } + return todo, nil +}