238 lines
5.6 KiB
Go
238 lines
5.6 KiB
Go
package utils
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/api/types/swarm"
|
|
"github.com/docker/docker/api/types/volume"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/errdefs"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
var (
|
|
ErrServiceNotFound = errors.New("unable to found the service")
|
|
ErrServiceCommandFailed = errors.New("command failed on the container")
|
|
ErrServiceUnhealthy = errors.New("service unhealthy")
|
|
)
|
|
|
|
type FnRetry func(ctx context.Context) error
|
|
|
|
func Retry(ctx context.Context, fnRetry FnRetry, waitDuration time.Duration, maxRetry int) (err error) {
|
|
for i := 0; i < maxRetry; i++ {
|
|
if err = fnRetry(ctx); err != nil {
|
|
time.Sleep(waitDuration)
|
|
continue
|
|
}
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
func CreateNetwork(ctx context.Context, cli *client.Client, networkName string) error {
|
|
networkSpec := types.NetworkCreate{
|
|
Driver: "overlay",
|
|
}
|
|
|
|
_, err := cli.NetworkCreate(ctx, networkName, networkSpec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func FilterImagesByName(ctx context.Context, cli *client.Client, name string) ([]types.ImageSummary, error) {
|
|
images, err := cli.ImageList(ctx, types.ImageListOptions{All: true})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var filteredImages []types.ImageSummary
|
|
|
|
for i := range images {
|
|
for _, tag := range images[i].RepoTags {
|
|
if tag == name {
|
|
filteredImages = append(filteredImages, images[i])
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return filteredImages, nil
|
|
}
|
|
|
|
func CreateService(ctx context.Context, cli *client.Client, spec *swarm.ServiceSpec) error {
|
|
serviceName := spec.Annotations.Name
|
|
log.Info().Str("service", serviceName).Msg("creating service...")
|
|
|
|
response, err := cli.ServiceCreate(ctx, *spec, types.ServiceCreateOptions{})
|
|
if err != nil {
|
|
if !errdefs.IsConflict(err) {
|
|
return err
|
|
}
|
|
|
|
log.Info().Str("service", serviceName).Msg("service already deployed")
|
|
return nil
|
|
}
|
|
|
|
log.Info().Str("service", serviceName).Str("id", response.ID).Msg("service deployed")
|
|
return nil
|
|
}
|
|
|
|
func GetServiceByName(ctx context.Context, cli *client.Client, name string) (swarm.Service, error) {
|
|
servicesFilters := filters.NewArgs()
|
|
servicesFilters.Add("name", name)
|
|
|
|
options := types.ServiceListOptions{
|
|
Filters: servicesFilters,
|
|
Status: true,
|
|
}
|
|
|
|
services, err := cli.ServiceList(ctx, options)
|
|
if err != nil {
|
|
return swarm.Service{}, err
|
|
}
|
|
|
|
if len(services) != 1 {
|
|
return swarm.Service{}, ErrServiceNotFound
|
|
}
|
|
|
|
return services[0], nil
|
|
}
|
|
|
|
func CheckServiceHealth(ctx context.Context, cli *client.Client, serviceName string) error {
|
|
log.Info().Str("service", serviceName).Msg("healthchecking...")
|
|
service, err := GetServiceByName(ctx, cli, serviceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if service.ServiceStatus != nil && service.ServiceStatus.RunningTasks != 0 {
|
|
log.Info().Str("service", serviceName).Msg("service is running")
|
|
return nil
|
|
}
|
|
|
|
log.Info().Str("service", serviceName).Msg("service not already started, retrying...")
|
|
return errors.New("service unhealthy")
|
|
}
|
|
|
|
func CheckServiceHealthWithRetry(ctx context.Context, cli *client.Client, serviceName string) error {
|
|
fnRetry := func(ctx context.Context) error {
|
|
return CheckServiceHealth(ctx, cli, serviceName)
|
|
}
|
|
|
|
waitDuration := 5 * time.Second
|
|
maxRetry := 15
|
|
return Retry(ctx, fnRetry, waitDuration, maxRetry)
|
|
}
|
|
|
|
func CheckAndDeleteCompletedService(ctx context.Context, cli *client.Client, name string) error {
|
|
service, err := GetServiceByName(ctx, cli, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fnRetry := func(ctx context.Context) error {
|
|
taskFilters := filters.NewArgs()
|
|
taskFilters.Add("service", service.ID)
|
|
|
|
tasks, err := cli.TaskList(ctx, types.TaskListOptions{
|
|
Filters: taskFilters,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
completeTasks := 0
|
|
for idx := range tasks {
|
|
if tasks[idx].Status.State == swarm.TaskStateComplete {
|
|
completeTasks++
|
|
}
|
|
}
|
|
|
|
if completeTasks == 0 {
|
|
return errors.New("no completed tasks")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
maxAttempts := 20
|
|
if err := Retry(ctx, fnRetry, time.Second, maxAttempts); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := cli.ServiceRemove(ctx, service.ID); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateServiceByName updates force a service by its name. It will remove and recreate the service.
|
|
func UpdateServiceByName(ctx context.Context, cli *client.Client, serviceName string) error {
|
|
srv, err := GetServiceByName(ctx, cli, serviceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := RemoveService(ctx, cli, srv.ID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := CreateService(ctx, cli, &srv.Spec); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func RemoveService(ctx context.Context, cli *client.Client, serviceID string) error {
|
|
fnRetry := func(ctx context.Context) error {
|
|
if err := cli.ServiceRemove(ctx, serviceID); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
maxRetry := 10
|
|
if err := Retry(ctx, fnRetry, time.Second, maxRetry); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func RemoveImage(ctx context.Context, cli *client.Client, imageID string) error {
|
|
fnRetry := func(ctx context.Context) error {
|
|
if _, err := cli.ImageRemove(ctx, imageID, types.ImageRemoveOptions{}); err != nil {
|
|
if errdefs.IsConflict(err) {
|
|
log.Warn().Str("image", imageID).Msg("image is using")
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
maxRetry := 10
|
|
if err := Retry(ctx, fnRetry, time.Second, maxRetry); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func CreateVolume(ctx context.Context, cli *client.Client, volumeName string) error {
|
|
if _, err := cli.VolumeCreate(ctx, volume.CreateOptions{Name: volumeName}); err != nil {
|
|
if !errdefs.IsConflict(err) {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|