internal/daemon/wait.go (54 lines of code) (raw):
package daemon
import (
"context"
"errors"
"fmt"
"time"
"go.uber.org/zap"
)
// WaitForStatus waits for the daemon to reach the desired status.
// It will keep checking the status of the daemon until it reaches the desired status
// or the context is cancelled. If you don't cancel the context, this function will
// keep retrying indefinitely.
func WaitForStatus(ctx context.Context, logger *zap.Logger, manager DaemonManager, daemonName string, desired DaemonStatus, backoff time.Duration) error {
for {
status, err := manager.GetDaemonStatus(daemonName)
if err != nil {
logger.Error("Failed to get daemon status", zap.String("daemon", daemonName), zap.Error(err))
} else {
if status == desired {
return nil
}
logger.Info("Daemon is not in the desired state yet", zap.String("daemon", daemonName), zap.String("status", string(status)))
}
logger.Debug("Waiting before next retry", zap.Duration("backoff", backoff))
select {
case <-ctx.Done():
return fmt.Errorf("daemon %s still has status %s: %w", daemonName, status, ctx.Err())
case <-time.After(backoff):
}
}
}
// AsyncOperation is a daemon operation that performs an action in the background.
type AsyncOperation func(ctx context.Context, daemonName string, opts ...OperationOption) error
// WaitForOperation waits for an asynchronous operation to complete. If the operation returns a Failed or Timeout
// result, an error is returned. If the context is cancelled, an error is returned. If the operation completes
// with any other result (Done, Cancelled, Dependency, Skipped), nil is returned.
func WaitForOperation(ctx context.Context, op AsyncOperation, name string, opts ...OperationOption) error {
o := &OperationOptions{}
for _, opt := range opts {
opt(o)
}
if o.Result != nil {
return errors.New("cannot specify a result channel when waiting for an operation")
}
result := make(chan OperationResult)
o.Result = result
if err := op(ctx, name, o.ApplyAll); err != nil {
return err
}
select {
case <-ctx.Done():
return fmt.Errorf("operation for daemon %s did not complete in time, result is unknown: %w", name, ctx.Err())
case res := <-result:
switch res {
case Failed, Timeout:
return fmt.Errorf("operation for daemon %s failed with result [%s]", name, res)
case Done, Canceled, Dependency, Skipped:
return nil
}
}
return nil
}