"git.arvados.org/arvados.git/lib/controller/dblock"
"git.arvados.org/arvados.git/lib/ctrlctx"
+ "github.com/sirupsen/logrus"
)
type railsDatabase struct{}
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 {
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
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
+}