init repository
This commit is contained in:
+237
@@ -0,0 +1,237 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user