mirror of https://github.com/docker/cli.git
75 lines
2.0 KiB
Go
75 lines
2.0 KiB
Go
package containerized
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/containerd/containerd"
|
|
"github.com/containerd/containerd/errdefs"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// AtomicImageUpdate will perform an update of the given container with the new image
|
|
// and verify success via the provided healthcheckFn. If the healthcheck fails, the
|
|
// container will be reverted to the prior image
|
|
func AtomicImageUpdate(ctx context.Context, container containerd.Container, image containerd.Image, healthcheckFn func() error) error {
|
|
updateCompleted := false
|
|
err := pauseAndRun(ctx, container, func() error {
|
|
if err := container.Update(ctx, WithUpgrade(image)); err != nil {
|
|
return errors.Wrap(err, "failed to update to new image")
|
|
}
|
|
updateCompleted = true
|
|
task, err := container.Task(ctx, nil)
|
|
if err != nil {
|
|
if errdefs.IsNotFound(err) {
|
|
return nil
|
|
}
|
|
return errors.Wrap(err, "failed to lookup task")
|
|
}
|
|
return task.Kill(ctx, sigTERM)
|
|
})
|
|
if err != nil {
|
|
if updateCompleted {
|
|
logrus.WithError(err).Error("failed to update, rolling back")
|
|
return rollBack(ctx, container)
|
|
}
|
|
return err
|
|
}
|
|
if err := healthcheckFn(); err != nil {
|
|
logrus.WithError(err).Error("failed health check, rolling back")
|
|
return rollBack(ctx, container)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func rollBack(ctx context.Context, container containerd.Container) error {
|
|
return pauseAndRun(ctx, container, func() error {
|
|
if err := container.Update(ctx, WithRollback); err != nil {
|
|
return err
|
|
}
|
|
task, err := container.Task(ctx, nil)
|
|
if err != nil {
|
|
if errdefs.IsNotFound(err) {
|
|
return nil
|
|
}
|
|
return errors.Wrap(err, "failed to lookup task")
|
|
}
|
|
return task.Kill(ctx, sigTERM)
|
|
})
|
|
}
|
|
|
|
func pauseAndRun(ctx context.Context, container containerd.Container, fn func() error) error {
|
|
task, err := container.Task(ctx, nil)
|
|
if err != nil {
|
|
if errdefs.IsNotFound(err) {
|
|
return fn()
|
|
}
|
|
return errors.Wrap(err, "failed to lookup task")
|
|
}
|
|
if err := task.Pause(ctx); err != nil {
|
|
return errors.Wrap(err, "failed to pause task")
|
|
}
|
|
defer task.Resume(ctx)
|
|
return fn()
|
|
}
|