localenv/utils/utils.go
2023-08-05 22:10:48 +02:00

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
}