implement --hooks-async
This commit is contained in:
parent
534976a97c
commit
1e02613a51
9
main.go
9
main.go
|
|
@ -207,7 +207,8 @@ func main() {
|
||||||
flGroupWrite := pflag.Bool("group-write",
|
flGroupWrite := pflag.Bool("group-write",
|
||||||
envBool(false, "GITSYNC_GROUP_WRITE", "GIT_SYNC_GROUP_WRITE"),
|
envBool(false, "GITSYNC_GROUP_WRITE", "GIT_SYNC_GROUP_WRITE"),
|
||||||
"ensure that all data (repo, worktrees, etc.) is group writable")
|
"ensure that all data (repo, worktrees, etc.) is group writable")
|
||||||
flStaleWorktreeTimeout := pflag.Duration("stale-worktree-timeout", envDuration(0, "GITSYNC_STALE_WORKTREE_TIMEOUT"),
|
flStaleWorktreeTimeout := pflag.Duration("stale-worktree-timeout",
|
||||||
|
envDuration(0, "GITSYNC_STALE_WORKTREE_TIMEOUT"),
|
||||||
"how long to retain non-current worktrees")
|
"how long to retain non-current worktrees")
|
||||||
|
|
||||||
flExechookCommand := pflag.String("exechook-command",
|
flExechookCommand := pflag.String("exechook-command",
|
||||||
|
|
@ -236,6 +237,10 @@ func main() {
|
||||||
envDuration(3*time.Second, "GITSYNC_WEBHOOK_BACKOFF", "GIT_SYNC_WEBHOOK_BACKOFF"),
|
envDuration(3*time.Second, "GITSYNC_WEBHOOK_BACKOFF", "GIT_SYNC_WEBHOOK_BACKOFF"),
|
||||||
"the time to wait before retrying a failed webhook")
|
"the time to wait before retrying a failed webhook")
|
||||||
|
|
||||||
|
flHooksAsync := pflag.Bool("hooks-async",
|
||||||
|
envBool(true, "GITSYNC_HOOKS_ASYNC", "GIT_SYNC_HOOKS_ASYNC"),
|
||||||
|
"run hooks asynchronously")
|
||||||
|
|
||||||
flUsername := pflag.String("username",
|
flUsername := pflag.String("username",
|
||||||
envString("", "GITSYNC_USERNAME", "GIT_SYNC_USERNAME"),
|
envString("", "GITSYNC_USERNAME", "GIT_SYNC_USERNAME"),
|
||||||
"the username to use for git auth")
|
"the username to use for git auth")
|
||||||
|
|
@ -844,6 +849,7 @@ func main() {
|
||||||
hook.NewHookData(),
|
hook.NewHookData(),
|
||||||
log,
|
log,
|
||||||
*flOneTime,
|
*flOneTime,
|
||||||
|
*flHooksAsync,
|
||||||
)
|
)
|
||||||
go webhookRunner.Run(context.Background())
|
go webhookRunner.Run(context.Background())
|
||||||
}
|
}
|
||||||
|
|
@ -868,6 +874,7 @@ func main() {
|
||||||
hook.NewHookData(),
|
hook.NewHookData(),
|
||||||
log,
|
log,
|
||||||
*flOneTime,
|
*flOneTime,
|
||||||
|
*flHooksAsync,
|
||||||
)
|
)
|
||||||
go exechookRunner.Run(context.Background())
|
go exechookRunner.Run(context.Background())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,10 +89,10 @@ func (d *hookData) send(newHash string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHookRunner returns a new HookRunner.
|
// NewHookRunner returns a new HookRunner.
|
||||||
func NewHookRunner(hook Hook, backoff time.Duration, data *hookData, log logintf, oneTime bool) *HookRunner {
|
func NewHookRunner(hook Hook, backoff time.Duration, data *hookData, log logintf, oneTime bool, async bool) *HookRunner {
|
||||||
hr := &HookRunner{hook: hook, backoff: backoff, data: data, log: log}
|
hr := &HookRunner{hook: hook, backoff: backoff, data: data, log: log, oneTime: oneTime, async: async}
|
||||||
if oneTime {
|
if oneTime || !async {
|
||||||
hr.oneTimeResult = make(chan bool, 1)
|
hr.result = make(chan bool, 1)
|
||||||
}
|
}
|
||||||
return hr
|
return hr
|
||||||
}
|
}
|
||||||
|
|
@ -107,9 +107,13 @@ type HookRunner struct {
|
||||||
data *hookData
|
data *hookData
|
||||||
// Logger
|
// Logger
|
||||||
log logintf
|
log logintf
|
||||||
// Used to send a status result when running in one-time mode.
|
// Used to send a status result when running in one-time or non-async mode.
|
||||||
// Should be initialised to a buffered channel of size 1.
|
// Should be initialised to a buffered channel of size 1.
|
||||||
oneTimeResult chan bool
|
result chan bool
|
||||||
|
// Bool for whether this is a one-time hook or not.
|
||||||
|
oneTime bool
|
||||||
|
// Bool for whether this is an async hook or not.
|
||||||
|
async bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just the logr methods we need in this package.
|
// Just the logr methods we need in this package.
|
||||||
|
|
@ -120,8 +124,17 @@ type logintf interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send sends hash to hookdata.
|
// Send sends hash to hookdata.
|
||||||
func (r *HookRunner) Send(hash string) {
|
func (r *HookRunner) Send(hash string) error {
|
||||||
r.data.send(hash)
|
r.data.send(hash)
|
||||||
|
if !r.async {
|
||||||
|
r.log.V(1).Info("waiting for completion", "hash", hash, "name", r.hook.Name())
|
||||||
|
err := r.WaitForCompletion()
|
||||||
|
r.log.V(1).Info("hook completed", "hash", hash, "err", err, "name", r.hook.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run waits for trigger events from the channel, and run hook when triggered.
|
// Run waits for trigger events from the channel, and run hook when triggered.
|
||||||
|
|
@ -144,45 +157,49 @@ func (r *HookRunner) Run(ctx context.Context) {
|
||||||
r.log.Error(err, "hook failed", "hash", hash, "retry", r.backoff)
|
r.log.Error(err, "hook failed", "hash", hash, "retry", r.backoff)
|
||||||
updateHookRunCountMetric(r.hook.Name(), "error")
|
updateHookRunCountMetric(r.hook.Name(), "error")
|
||||||
// don't want to sleep unnecessarily terminating anyways
|
// don't want to sleep unnecessarily terminating anyways
|
||||||
r.sendOneTimeResultAndTerminate(false)
|
r.sendResult(false)
|
||||||
time.Sleep(r.backoff)
|
time.Sleep(r.backoff)
|
||||||
} else {
|
} else {
|
||||||
updateHookRunCountMetric(r.hook.Name(), "success")
|
updateHookRunCountMetric(r.hook.Name(), "success")
|
||||||
lastHash = hash
|
lastHash = hash
|
||||||
r.sendOneTimeResultAndTerminate(true)
|
r.sendResult(true)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If oneTimeResult is nil, does nothing. Otherwise, forwards the caller
|
func (r *HookRunner) sendResult(completedSuccessfully bool) {
|
||||||
// provided success status (as a boolean) of HookRunner to receivers of
|
// if onetime is true, we send the result then exit
|
||||||
// oneTimeResult, closes said chanel, and terminates this goroutine.
|
if r.oneTime {
|
||||||
// Using this function to write to oneTimeResult ensures it is only ever
|
r.result <- completedSuccessfully
|
||||||
// written to once.
|
close(r.result)
|
||||||
func (r *HookRunner) sendOneTimeResultAndTerminate(completedSuccessfully bool) {
|
|
||||||
if r.oneTimeResult != nil {
|
|
||||||
r.oneTimeResult <- completedSuccessfully
|
|
||||||
close(r.oneTimeResult)
|
|
||||||
runtime.Goexit()
|
runtime.Goexit()
|
||||||
|
} else if !r.async {
|
||||||
|
// if onetime is false, and we've set non-async we send but don't exit.
|
||||||
|
r.result <- completedSuccessfully
|
||||||
}
|
}
|
||||||
|
// if neither oneTime nor !async, we do nothing here.
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitForCompletion waits for HookRunner to send completion message to
|
// WaitForCompletion waits for HookRunner to send completion message to
|
||||||
// calling thread and returns either true if HookRunner executed successfully
|
// calling thread and returns either true if HookRunner executed successfully
|
||||||
// and some error otherwise.
|
// and some error otherwise.
|
||||||
// Assumes that r.oneTimeResult is not nil, but if it is, returns an error.
|
// Assumes that either r.oneTime or !r.async, otherwise returns an error.
|
||||||
func (r *HookRunner) WaitForCompletion() error {
|
func (r *HookRunner) WaitForCompletion() error {
|
||||||
// Make sure function should be called
|
// Make sure function should be called
|
||||||
if r.oneTimeResult == nil {
|
if r.result == nil {
|
||||||
return fmt.Errorf("HookRunner.WaitForCompletion called on async runner")
|
return fmt.Errorf("HookRunner.WaitForCompletion called on async runner")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform wait on HookRunner
|
// If oneTimeResult is not nil, we wait for its result.
|
||||||
hookRunnerFinishedSuccessfully := <-r.oneTimeResult
|
if r.result != nil {
|
||||||
if !hookRunnerFinishedSuccessfully {
|
hookRunnerFinishedSuccessfully := <-r.result
|
||||||
return fmt.Errorf("hook completed with error")
|
r.log.V(1).Info("hook completed", "success", hookRunnerFinishedSuccessfully,
|
||||||
|
"oneTime", r.oneTime, "async", r.async, "name", r.hook.Name())
|
||||||
|
if !hookRunnerFinishedSuccessfully {
|
||||||
|
return fmt.Errorf("hook completed with error")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
28
test_e2e.sh
28
test_e2e.sh
|
|
@ -2406,7 +2406,7 @@ function e2e::exechook_fail_retry() {
|
||||||
--exechook-command="/$EXECHOOK_COMMAND_FAIL" \
|
--exechook-command="/$EXECHOOK_COMMAND_FAIL" \
|
||||||
--exechook-backoff=1s \
|
--exechook-backoff=1s \
|
||||||
&
|
&
|
||||||
sleep 3 # give it time to retry
|
sleep 4 # give it time to retry
|
||||||
|
|
||||||
# Check that exechook was called
|
# Check that exechook was called
|
||||||
assert_file_lines_ge "$RUNLOG" 2
|
assert_file_lines_ge "$RUNLOG" 2
|
||||||
|
|
@ -2486,6 +2486,29 @@ function e2e::exechook_startup_after_crash() {
|
||||||
assert_file_lines_eq "$RUNLOG" 1
|
assert_file_lines_eq "$RUNLOG" 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
##############################################
|
||||||
|
# Test exechook-success with --hooks-async=false
|
||||||
|
##############################################
|
||||||
|
function e2e::exechook_success_hooks_non_async() {
|
||||||
|
cat /dev/null > "$RUNLOG"
|
||||||
|
|
||||||
|
GIT_SYNC \
|
||||||
|
--hooks-async=false \
|
||||||
|
--period=100ms \
|
||||||
|
--repo="file://$REPO" \
|
||||||
|
--root="$ROOT" \
|
||||||
|
--link="link" \
|
||||||
|
--exechook-command="/$EXECHOOK_COMMAND_SLEEPY" \
|
||||||
|
&
|
||||||
|
sleep 5 # give it time to run
|
||||||
|
assert_link_exists "$ROOT/link"
|
||||||
|
assert_file_exists "$ROOT/link/file"
|
||||||
|
assert_file_exists "$ROOT/link/exechook"
|
||||||
|
assert_file_eq "$ROOT/link/file" "${FUNCNAME[0]}"
|
||||||
|
assert_file_eq "$ROOT/link/exechook" "${FUNCNAME[0]}"
|
||||||
|
assert_file_eq "$ROOT/link/exechook-env" "$EXECHOOK_ENVKEY=$EXECHOOK_ENVVAL"
|
||||||
|
}
|
||||||
|
|
||||||
##############################################
|
##############################################
|
||||||
# Test webhook success
|
# Test webhook success
|
||||||
##############################################
|
##############################################
|
||||||
|
|
@ -3797,8 +3820,7 @@ for t; do
|
||||||
fi
|
fi
|
||||||
remove_containers || true
|
remove_containers || true
|
||||||
run=$((run+1))
|
run=$((run+1))
|
||||||
done
|
done if [[ "$test_ret" != 0 ]]; then
|
||||||
if [[ "$test_ret" != 0 ]]; then
|
|
||||||
final_ret=1
|
final_ret=1
|
||||||
failures+=("$t (log: ${log}.*)")
|
failures+=("$t (log: ${log}.*)")
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue