mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 23:16:37 +08:00
feat(core): add WithMigrationBaseline hook before goose Up
This commit is contained in:
+37
-30
@@ -59,6 +59,14 @@ func WithMigrationRunner(runner MigrationRunner) AppOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithMigrationBaseline registers a hook the migration engine runs after the
|
||||
// shared version table exists and before any plugin Up.
|
||||
func WithMigrationBaseline(fn func(*Context) error) AppOption {
|
||||
return func(a *App) {
|
||||
a.migrationBaseline = fn
|
||||
}
|
||||
}
|
||||
|
||||
// WithShutdownTimeout sets the fallback timeout for graceful application shutdown.
|
||||
func WithShutdownTimeout(timeout time.Duration) AppOption {
|
||||
return func(a *App) {
|
||||
@@ -99,23 +107,24 @@ func WithConfigDecl(pluginID string, bindings ...ConfigBinding) AppOption {
|
||||
// It manages plugin collection, dependency mounting, migration execution, profile-based driver startup,
|
||||
// and graceful signal-driven LIFO shutdown.
|
||||
type App struct {
|
||||
mu sync.RWMutex
|
||||
ctx *Context
|
||||
profile Profile
|
||||
plugins []Plugin
|
||||
pluginMap map[string]Plugin
|
||||
fibers []*Fiber
|
||||
fiberMap map[string]*Fiber
|
||||
applied bool
|
||||
running bool
|
||||
startedDrivers []Driver
|
||||
migrationEngine MigrationEngine
|
||||
shutdownTimeout time.Duration
|
||||
configSource ConfigSource
|
||||
hostDeclOwner string
|
||||
hostDeclBindings []ConfigBinding
|
||||
prepared bool
|
||||
applyErr error
|
||||
mu sync.RWMutex
|
||||
ctx *Context
|
||||
profile Profile
|
||||
plugins []Plugin
|
||||
pluginMap map[string]Plugin
|
||||
fibers []*Fiber
|
||||
fiberMap map[string]*Fiber
|
||||
applied bool
|
||||
running bool
|
||||
startedDrivers []Driver
|
||||
migrationEngine MigrationEngine
|
||||
migrationBaseline func(*Context) error
|
||||
shutdownTimeout time.Duration
|
||||
configSource ConfigSource
|
||||
hostDeclOwner string
|
||||
hostDeclBindings []ConfigBinding
|
||||
prepared bool
|
||||
applyErr error
|
||||
}
|
||||
|
||||
// NewApp creates a new Cordis application instance with default options.
|
||||
@@ -373,22 +382,20 @@ func (a *App) prepareLocked() error {
|
||||
if a.prepared {
|
||||
return nil
|
||||
}
|
||||
if a.configSource == nil {
|
||||
a.prepared = true
|
||||
return nil
|
||||
if a.configSource != nil {
|
||||
config := a.ctx.Config()
|
||||
config.SetSource(a.configSource)
|
||||
|
||||
if err := config.Declare(a.hostDeclOwner, a.hostDeclBindings...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := config.Resolve(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
config := a.ctx.Config()
|
||||
config.SetSource(a.configSource)
|
||||
|
||||
if err := config.Declare(a.hostDeclOwner, a.hostDeclBindings...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := config.Resolve(); err != nil {
|
||||
return err
|
||||
}
|
||||
a.ctx.setMigrationBaseline(a.migrationBaseline)
|
||||
a.prepared = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -594,3 +594,61 @@ func TestAppSetShutdownTimeoutIgnoresNonPositive(t *testing.T) {
|
||||
app.SetShutdownTimeout(45 * time.Second)
|
||||
assert.Equal(t, 45*time.Second, app.ShutdownTimeout())
|
||||
}
|
||||
|
||||
func TestWithMigrationBaselineVisibleAfterPrepare(t *testing.T) {
|
||||
var called bool
|
||||
fn := func(*core.Context) error {
|
||||
called = true
|
||||
return nil
|
||||
}
|
||||
|
||||
app := core.NewApp(core.WithMigrationBaseline(fn))
|
||||
require.Nil(t, app.Context().MigrationBaseline(), "baseline must be copied during Prepare")
|
||||
|
||||
require.NoError(t, app.Prepare())
|
||||
|
||||
got := app.Context().MigrationBaseline()
|
||||
require.NotNil(t, got, "Prepare must copy the baseline onto the root Context")
|
||||
require.NoError(t, got(app.Context()))
|
||||
assert.True(t, called)
|
||||
}
|
||||
|
||||
func TestWithMigrationBaselineRunsBeforeEngineMigrate(t *testing.T) {
|
||||
var order []string
|
||||
engine := core.MigrationRunner(func(ctx *core.Context, _ []extpoints.MigrationEntry) error {
|
||||
order = append(order, "engine")
|
||||
if ctx.MigrationBaseline() == nil {
|
||||
t.Fatal("baseline must be visible on context inside Migrate")
|
||||
}
|
||||
return ctx.MigrationBaseline()(ctx)
|
||||
})
|
||||
|
||||
sqlFS := fstest.MapFS{
|
||||
"migrations/001_init.sql": &fstest.MapFile{Data: []byte("-- +goose Up\nSELECT 1;\n")},
|
||||
}
|
||||
app := core.NewApp(
|
||||
core.WithMigrationEngine(engine),
|
||||
core.WithMigrationBaseline(func(*core.Context) error {
|
||||
order = append(order, "baseline")
|
||||
return nil
|
||||
}),
|
||||
core.WithPlugins(&appMockPlugin{
|
||||
name: "t",
|
||||
applyFn: func(ctx *core.Context) error {
|
||||
ctx.Migrations().Register("t", sqlFS)
|
||||
return nil
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
require.NoError(t, app.Start(context.Background()))
|
||||
defer func() { _ = app.Stop(context.Background()) }()
|
||||
|
||||
assert.Equal(t, []string{"engine", "baseline"}, order)
|
||||
}
|
||||
|
||||
func TestWithMigrationBaselineNilByDefault(t *testing.T) {
|
||||
app := core.NewApp()
|
||||
require.NoError(t, app.Prepare())
|
||||
assert.Nil(t, app.Context().MigrationBaseline())
|
||||
}
|
||||
|
||||
+47
-18
@@ -29,12 +29,13 @@ type Context struct {
|
||||
settings extpoints.SettingExtension
|
||||
config extpoints.ConfigExtension
|
||||
|
||||
mu sync.RWMutex
|
||||
children []*Context
|
||||
disposers []Disposer
|
||||
drivers []Driver
|
||||
values map[any]any
|
||||
disposed bool
|
||||
mu sync.RWMutex
|
||||
children []*Context
|
||||
disposers []Disposer
|
||||
drivers []Driver
|
||||
values map[any]any
|
||||
disposed bool
|
||||
migrationBaseline func(*Context) error
|
||||
}
|
||||
|
||||
// NewContext creates a new root Context wrapping a standard Go context.
|
||||
@@ -159,18 +160,19 @@ func (c *Context) ForkWithContext(base context.Context) *Context {
|
||||
ctx, cancel := context.WithCancel(base)
|
||||
|
||||
child := &Context{
|
||||
goCtx: ctx,
|
||||
cancel: cancel,
|
||||
parent: c,
|
||||
container: NewContainer(c.container),
|
||||
events: c.events,
|
||||
router: c.router,
|
||||
migrations: c.migrations,
|
||||
tasks: c.tasks,
|
||||
schedules: c.schedules,
|
||||
settings: c.settings,
|
||||
config: c.config,
|
||||
values: make(map[any]any),
|
||||
goCtx: ctx,
|
||||
cancel: cancel,
|
||||
parent: c,
|
||||
container: NewContainer(c.container),
|
||||
events: c.events,
|
||||
router: c.router,
|
||||
migrations: c.migrations,
|
||||
tasks: c.tasks,
|
||||
schedules: c.schedules,
|
||||
settings: c.settings,
|
||||
config: c.config,
|
||||
values: make(map[any]any),
|
||||
migrationBaseline: c.MigrationBaseline(),
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
@@ -208,6 +210,33 @@ func (c *Context) Migrations() extpoints.MigrationExtension {
|
||||
return c.migrations
|
||||
}
|
||||
|
||||
// MigrationBaseline returns the hook copied onto this Context during App.Prepare.
|
||||
// Child contexts fall back to their parent so forks still see the root hook.
|
||||
func (c *Context) MigrationBaseline() func(*Context) error {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
c.mu.RLock()
|
||||
fn := c.migrationBaseline
|
||||
c.mu.RUnlock()
|
||||
if fn != nil {
|
||||
return fn
|
||||
}
|
||||
if c.parent != nil {
|
||||
return c.parent.MigrationBaseline()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Context) setMigrationBaseline(fn func(*Context) error) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.migrationBaseline = fn
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Tasks returns the scoped TaskExtension registry with automatic disposer tracking.
|
||||
func (c *Context) Tasks() extpoints.TaskExtension {
|
||||
return newScopedTaskExtension(c, c.tasks)
|
||||
|
||||
Reference in New Issue
Block a user