Skip to content

Commit 77d13cc

Browse files
committed
Add MigrationContext.Hooks for in-process hook implementations
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.
1 parent 494570d commit 77d13cc

7 files changed

Lines changed: 348 additions & 37 deletions

File tree

doc/hooks.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,35 @@ The following variable are available on particular hooks:
8989
### Examples
9090

9191
See [sample hooks](https://github.com/github/gh-ost/tree/master/resources/hooks-sample), as `bash` implementation samples.
92+
93+
### Embedded usage: registering Go callbacks
94+
95+
When `gh-ost` is consumed as a library (importing `github.com/github/gh-ost/go/logic`), callers can register Go functions for any hook event instead of, or in addition to, the on-disk script contract. Implement the `base.Hooks` interface and assign it to `MigrationContext.Hooks` before calling `logic.NewMigrator`:
96+
97+
```go
98+
import (
99+
"github.com/github/gh-ost/go/base"
100+
"github.com/github/gh-ost/go/logic"
101+
)
102+
103+
type myHooks struct{}
104+
105+
func (myHooks) OnSuccess(instantDDL bool) error { return nil }
106+
func (myHooks) OnFailure() error { return nil }
107+
// ... implement the remaining base.Hooks methods.
108+
109+
ctx.Hooks = &myHooks{}
110+
m := logic.NewMigrator(ctx, version)
111+
err := m.Migrate()
112+
```
113+
114+
To run shell hooks from `--hooks-path` and Go callbacks together, wrap both in `logic.CompositeHooks`. Each member is invoked in order; the first non-nil error short-circuits, matching the script executor's behavior:
115+
116+
```go
117+
ctx.Hooks = logic.CompositeHooks{
118+
logic.NewHooksExecutor(ctx), // existing scripts under HooksPath
119+
&myHooks{}, // additional Go callbacks
120+
}
121+
```
122+
123+
`MigrationContext.Hooks` is opt-in. When it is nil, `NewMigrator` wires the default script executor and behavior is identical to the CLI. Hooks are read once at `NewMigrator` time, so reassigning the field afterwards has no effect on the running migration.

go/base/context.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ type MigrationContext struct {
156156
HooksHintOwner string
157157
HooksHintToken string
158158
HooksStatusIntervalSec int64
159+
Hooks Hooks
159160
PanicOnWarnings bool
160161
Checkpoint bool
161162
CheckpointIntervalSeconds int64

go/base/hooks.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
Copyright 2026 GitHub Inc.
3+
See https://github.com/github/gh-ost/blob/master/LICENSE
4+
*/
5+
6+
package base
7+
8+
// Hooks is the set of lifecycle callbacks gh-ost invokes during a migration.
9+
type Hooks interface {
10+
OnStartup() error
11+
OnValidated() error
12+
OnRowCountComplete() error
13+
OnBeforeRowCopy() error
14+
OnRowCopyComplete() error
15+
OnBeginPostponed() error
16+
OnBeforeCutOver() error
17+
OnInteractiveCommand(command string) error
18+
OnSuccess(instantDDL bool) error
19+
OnFailure() error
20+
OnBatchCopyRetry(errorMessage string) error
21+
OnStatus(statusMessage string) error
22+
OnStopReplication() error
23+
OnStartReplication() error
24+
}

go/logic/hooks.go

Lines changed: 143 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,135 @@ const (
3434
onStartReplication = "gh-ost-on-start-replication"
3535
)
3636

37+
// CompositeHooks invokes each member in order, returning the first non-nil error.
38+
type CompositeHooks []base.Hooks
39+
40+
func (c CompositeHooks) OnStartup() error {
41+
for _, h := range c {
42+
if err := h.OnStartup(); err != nil {
43+
return err
44+
}
45+
}
46+
return nil
47+
}
48+
49+
func (c CompositeHooks) OnValidated() error {
50+
for _, h := range c {
51+
if err := h.OnValidated(); err != nil {
52+
return err
53+
}
54+
}
55+
return nil
56+
}
57+
58+
func (c CompositeHooks) OnRowCountComplete() error {
59+
for _, h := range c {
60+
if err := h.OnRowCountComplete(); err != nil {
61+
return err
62+
}
63+
}
64+
return nil
65+
}
66+
67+
func (c CompositeHooks) OnBeforeRowCopy() error {
68+
for _, h := range c {
69+
if err := h.OnBeforeRowCopy(); err != nil {
70+
return err
71+
}
72+
}
73+
return nil
74+
}
75+
76+
func (c CompositeHooks) OnRowCopyComplete() error {
77+
for _, h := range c {
78+
if err := h.OnRowCopyComplete(); err != nil {
79+
return err
80+
}
81+
}
82+
return nil
83+
}
84+
85+
func (c CompositeHooks) OnBeginPostponed() error {
86+
for _, h := range c {
87+
if err := h.OnBeginPostponed(); err != nil {
88+
return err
89+
}
90+
}
91+
return nil
92+
}
93+
94+
func (c CompositeHooks) OnBeforeCutOver() error {
95+
for _, h := range c {
96+
if err := h.OnBeforeCutOver(); err != nil {
97+
return err
98+
}
99+
}
100+
return nil
101+
}
102+
103+
func (c CompositeHooks) OnInteractiveCommand(command string) error {
104+
for _, h := range c {
105+
if err := h.OnInteractiveCommand(command); err != nil {
106+
return err
107+
}
108+
}
109+
return nil
110+
}
111+
112+
func (c CompositeHooks) OnSuccess(instantDDL bool) error {
113+
for _, h := range c {
114+
if err := h.OnSuccess(instantDDL); err != nil {
115+
return err
116+
}
117+
}
118+
return nil
119+
}
120+
121+
func (c CompositeHooks) OnFailure() error {
122+
for _, h := range c {
123+
if err := h.OnFailure(); err != nil {
124+
return err
125+
}
126+
}
127+
return nil
128+
}
129+
130+
func (c CompositeHooks) OnBatchCopyRetry(errorMessage string) error {
131+
for _, h := range c {
132+
if err := h.OnBatchCopyRetry(errorMessage); err != nil {
133+
return err
134+
}
135+
}
136+
return nil
137+
}
138+
139+
func (c CompositeHooks) OnStatus(statusMessage string) error {
140+
for _, h := range c {
141+
if err := h.OnStatus(statusMessage); err != nil {
142+
return err
143+
}
144+
}
145+
return nil
146+
}
147+
148+
func (c CompositeHooks) OnStopReplication() error {
149+
for _, h := range c {
150+
if err := h.OnStopReplication(); err != nil {
151+
return err
152+
}
153+
}
154+
return nil
155+
}
156+
157+
func (c CompositeHooks) OnStartReplication() error {
158+
for _, h := range c {
159+
if err := h.OnStartReplication(); err != nil {
160+
return err
161+
}
162+
}
163+
return nil
164+
}
165+
37166
type HooksExecutor struct {
38167
migrationContext *base.MigrationContext
39168
writer io.Writer
@@ -111,61 +240,61 @@ func (he *HooksExecutor) executeHooks(baseName string, extraVariables ...string)
111240
return nil
112241
}
113242

114-
func (he *HooksExecutor) onStartup() error {
243+
func (he *HooksExecutor) OnStartup() error {
115244
return he.executeHooks(onStartup)
116245
}
117246

118-
func (he *HooksExecutor) onValidated() error {
247+
func (he *HooksExecutor) OnValidated() error {
119248
return he.executeHooks(onValidated)
120249
}
121250

122-
func (he *HooksExecutor) onRowCountComplete() error {
251+
func (he *HooksExecutor) OnRowCountComplete() error {
123252
return he.executeHooks(onRowCountComplete)
124253
}
125-
func (he *HooksExecutor) onBeforeRowCopy() error {
254+
func (he *HooksExecutor) OnBeforeRowCopy() error {
126255
return he.executeHooks(onBeforeRowCopy)
127256
}
128257

129-
func (he *HooksExecutor) onBatchCopyRetry(errorMessage string) error {
258+
func (he *HooksExecutor) OnBatchCopyRetry(errorMessage string) error {
130259
v := fmt.Sprintf("GH_OST_LAST_BATCH_COPY_ERROR=%s", errorMessage)
131260
return he.executeHooks(onBatchCopyRetry, v)
132261
}
133262

134-
func (he *HooksExecutor) onRowCopyComplete() error {
263+
func (he *HooksExecutor) OnRowCopyComplete() error {
135264
return he.executeHooks(onRowCopyComplete)
136265
}
137266

138-
func (he *HooksExecutor) onBeginPostponed() error {
267+
func (he *HooksExecutor) OnBeginPostponed() error {
139268
return he.executeHooks(onBeginPostponed)
140269
}
141270

142-
func (he *HooksExecutor) onBeforeCutOver() error {
271+
func (he *HooksExecutor) OnBeforeCutOver() error {
143272
return he.executeHooks(onBeforeCutOver)
144273
}
145274

146-
func (he *HooksExecutor) onInteractiveCommand(command string) error {
275+
func (he *HooksExecutor) OnInteractiveCommand(command string) error {
147276
v := fmt.Sprintf("GH_OST_COMMAND='%s'", command)
148277
return he.executeHooks(onInteractiveCommand, v)
149278
}
150279

151-
func (he *HooksExecutor) onSuccess(instantDDL bool) error {
280+
func (he *HooksExecutor) OnSuccess(instantDDL bool) error {
152281
v := fmt.Sprintf("GH_OST_INSTANT_DDL=%t", instantDDL)
153282
return he.executeHooks(onSuccess, v)
154283
}
155284

156-
func (he *HooksExecutor) onFailure() error {
285+
func (he *HooksExecutor) OnFailure() error {
157286
return he.executeHooks(onFailure)
158287
}
159288

160-
func (he *HooksExecutor) onStatus(statusMessage string) error {
289+
func (he *HooksExecutor) OnStatus(statusMessage string) error {
161290
v := fmt.Sprintf("GH_OST_STATUS='%s'", statusMessage)
162291
return he.executeHooks(onStatus, v)
163292
}
164293

165-
func (he *HooksExecutor) onStopReplication() error {
294+
func (he *HooksExecutor) OnStopReplication() error {
166295
return he.executeHooks(onStopReplication)
167296
}
168297

169-
func (he *HooksExecutor) onStartReplication() error {
298+
func (he *HooksExecutor) OnStartReplication() error {
170299
return he.executeHooks(onStartReplication)
171300
}

0 commit comments

Comments
 (0)