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

190 lines
3.6 KiB
Go

package collector
import (
"context"
"errors"
"io"
"os"
"path"
"strings"
"sync"
"time"
"localenv/utils"
"github.com/docker/docker/client"
"github.com/rs/zerolog/log"
)
const HostImagesStore string = "/tmp/localenv-swarm"
var ErrImageNotFound = errors.New("image not found")
func imageNameIntoFilename(name string) string {
name = strings.Replace(name, "/", "-", -1)
return strings.Replace(name, ":", "-", -1)
}
type collectorErr struct {
errs []error
sync.Mutex
}
func newCollectorError() collectorErr {
return collectorErr{
errs: []error{},
}
}
func (c *collectorErr) AddError(err error) {
c.Lock()
defer c.Unlock()
c.errs = append(c.errs, err)
}
func (c *collectorErr) Err() error {
if len(c.errs) != 0 {
return errors.Join(c.errs...)
}
return nil
}
type Collector struct {
hostCLI *client.Client
swarmCLI *client.Client
}
func NewCollector(hostCLI, swarmCLI *client.Client) Collector {
return Collector{
hostCLI: hostCLI,
swarmCLI: swarmCLI,
}
}
func (c Collector) saveImage(ctx context.Context, name, filepath string) error {
log.Info().Str("image", name).Str("path", filepath).Msg("saving image into store...")
imageSaveResponse, err := c.hostCLI.ImageSave(ctx, []string{name})
if err != nil {
return err
}
defer imageSaveResponse.Close()
tarFile, err := os.Create(filepath)
if err != nil {
return err
}
defer tarFile.Close()
content, err := io.ReadAll(imageSaveResponse)
if err != nil {
return err
}
if _, err := tarFile.Write(content); err != nil {
return err
}
log.Info().Str("image", name).Str("path", filepath).Msg("image saved successfully")
return nil
}
func (c Collector) loadImage(ctx context.Context, filepath string) error {
log.Info().Str("path", filepath).Msg("loading image...")
tarFile, err := os.Open(filepath)
if err != nil {
return err
}
defer tarFile.Close()
loadResponse, err := c.swarmCLI.ImageLoad(ctx, tarFile, false)
if err != nil {
return err
}
defer loadResponse.Body.Close()
log.Info().Str("path", filepath).Msg("image loaded successfully")
return nil
}
func (c Collector) retryCheckImage(ctx context.Context, name string) error {
fnRetry := func(ctx context.Context) error {
ok, err := c.checkImage(ctx, name)
if err != nil {
log.Warn().Str("image", name).Msg("error while searching image, retrying...")
return err
}
if !ok {
log.Warn().Str("image", name).Msg("image not found, retrying...")
return ErrImageNotFound
}
return nil
}
attempts := 30
waitDuration := 2 * time.Second
if err := utils.Retry(ctx, fnRetry, waitDuration, attempts); err != nil {
return err
}
return nil
}
func (c Collector) checkImage(ctx context.Context, name string) (bool, error) {
images, err := utils.FilterImagesByName(ctx, c.swarmCLI, name)
if err != nil {
return false, err
}
if len(images) != 1 {
return false, nil
}
return true, nil
}
func (c Collector) DeployImages(ctx context.Context, images []string) error {
errs := newCollectorError()
var wg sync.WaitGroup
wg.Add(len(images))
for i := range images {
go func(idx int) {
defer wg.Done()
ok, err := c.checkImage(ctx, images[idx])
if err != nil {
errs.AddError(err)
}
if ok {
return
}
filepath := path.Join(HostImagesStore, imageNameIntoFilename(images[idx])+".tar")
if err = c.saveImage(ctx, images[idx], filepath); err != nil {
errs.AddError(err)
}
if err = c.loadImage(ctx, filepath); err != nil {
errs.AddError(err)
}
if err := c.retryCheckImage(ctx, images[idx]); err != nil {
errs.AddError(err)
}
}(i)
}
wg.Wait()
return errs.Err()
}