Skip to content

Add MigrationContext.Hooks for in-process hook implementations#1675

Merged
meiji163 merged 2 commits into
github:masterfrom
olsonjp:feature/pluggable-go-hooks
May 14, 2026
Merged

Add MigrationContext.Hooks for in-process hook implementations#1675
meiji163 merged 2 commits into
github:masterfrom
olsonjp:feature/pluggable-go-hooks

Conversation

@olsonjp
Copy link
Copy Markdown
Contributor

@olsonjp olsonjp commented May 14, 2026

Related issue: #1674

Description

This PR adds an additive extension point that lets library callers register Go hook implementations instead of (or alongside) the on-disk script contract behind --hooks-path.

  • Introduces a Hooks interface in go/base with one method per lifecycle event.
  • Adds an optional MigrationContext.Hooks field. logic.NewMigrator reads it once at construction and falls back to the existing HooksExecutor when unset, so default behavior is unchanged for CLI users.
  • Adds logic.CompositeHooks for callers who want to layer Go callbacks on top of the on-disk script executor.
  • Renames the previously package-private HooksExecutor method names (onStartupOnStartup, etc.) so external types can satisfy the interface. The struct and constructor were already exported, but the methods weren't, so this isn't displacing any usable external API.

Open questions

A couple of design choices I'd appreciate input on:

  • Where does the Hooks interface belong? The PR puts it in go/base next to HooksPath / HooksHintMessage / HooksHintOwner / HooksHintToken, which already act as the hook-config home on MigrationContext. Living in go/logic next to HooksExecutor is also defensible — but would either require MigrationContext to hold the field as any and type-assert, or invert the import direction.
  • Method-name exports. This is the only piece of public surface the PR changes. As noted above, the methods were unreachable from outside go/logic, so I don't believe this displaces real usage — but happy to take a different shape (e.g. keep onXxx and add exported shims) if you'd rather leave the existing names alone.

Tests

  • The existing TestHooksExecutorExecuteHooks (and its does-not-exist / failed / success subtests covering the CLI path) is unchanged and still passes.
  • New unit tests cover CompositeHooks ordering and short-circuit semantics, and NewMigrator honoring MigrationContext.Hooks.
  • Full local integration suite (script/docker-gh-ost-replica-tests) passes (88/88, 1 sysbench skip).

gh-ost's only hook extension point is on-disk scripts globbed from
--hooks-path. Library callers that embed Migrator must either ship
scripts and their dependencies alongside their binary or maintain a
parallel Go layer that bridges script side effects back into the host
application.

Introduce a Hooks interface in go/base with one method per lifecycle
event, and an optional MigrationContext.Hooks field. NewMigrator reads
the field once at construction and falls back to the existing
HooksExecutor when unset, so CLI behavior is unchanged. A CompositeHooks
helper in go/logic lets callers run the on-disk script executor and
their own Go implementation side-by-side.

HooksExecutor's previously package-private method names are renamed
(onStartup -> OnStartup, etc.) so external types can satisfy the
interface. The struct and constructor were already exported but the
methods weren't, so no usable external API is displaced.
Copilot AI review requested due to automatic review settings May 14, 2026 17:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an opt-in in-process hooks extension point for library consumers of logic.NewMigrator, allowing hook lifecycle events to be handled by Go implementations (and optionally composed with the existing --hooks-path shell script executor) while keeping CLI/default behavior unchanged.

Changes:

  • Introduces base.Hooks and adds MigrationContext.Hooks as an optional override for hook handling.
  • Updates the migrator/server to depend on the base.Hooks interface, defaulting to logic.HooksExecutor when no hooks are provided.
  • Adds logic.CompositeHooks plus unit tests and documentation for embedded usage.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
go/logic/server.go Switches server hook dependency from *HooksExecutor to base.Hooks and calls exported hook methods.
go/logic/migrator.go Reads MigrationContext.Hooks once during construction, defaulting to the shell-based executor when unset.
go/logic/hooks.go Adds CompositeHooks and exports HooksExecutor lifecycle methods to satisfy base.Hooks.
go/logic/hooks_test.go Adds tests for CompositeHooks ordering/short-circuiting and NewMigrator hook selection.
go/base/hooks.go Defines the new base.Hooks interface.
go/base/context.go Adds Hooks base.Hooks to MigrationContext.
doc/hooks.md Documents embedded usage via MigrationContext.Hooks and composing with logic.CompositeHooks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread go/logic/migrator.go
Comment on lines 105 to +109
func NewMigrator(context *base.MigrationContext, appVersion string) *Migrator {
hooks := context.Hooks
if hooks == nil {
hooks = NewHooksExecutor(context)
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declined. MigrationContext.Log (interface Logger) is the same pattern in this codebase: swap-able by library callers, used as mctx.Log.Infof(...) throughout without nil checks, and exposed to the same typed-nil hazard. Defending against typed-nil here would be inconsistent with the existing style.

Comment thread go/logic/hooks.go
Comment on lines +40 to +45
func (c CompositeHooks) OnStartup() error {
for _, h := range c {
if err := h.OnStartup(); err != nil {
return err
}
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0927d5f — each fan-out method now skips nil members, with a TestCompositeHooks_SkipsNil test.

Comment thread doc/hooks.md
Comment on lines +108 to +109

ctx.Hooks = &myHooks{}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0927d5f — snippet now declares version and constructs ctx via base.NewMigrationContext().

Address PR review feedback:

- CompositeHooks.OnX methods skip nil members instead of panicking,
  allowing callers to conditionally append optional hooks.
- doc/hooks.md embedded-usage snippet now defines ctx and version so it
  is self-contained.
@meiji163
Copy link
Copy Markdown
Contributor

Great feature, thanks! As for the location of the Hooks interface and exporting the hook methods, I am good with both as they are in your PR 👍

@meiji163 meiji163 merged commit e62debe into github:master May 14, 2026
9 checks passed
@olsonjp olsonjp deleted the feature/pluggable-go-hooks branch May 14, 2026 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants