hmdeploy/deployers/swarm.go
2025-04-02 12:19:45 +02:00

168 lines
4.0 KiB
Go

package deployers
import (
"archive/tar"
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"github.com/rs/zerolog/log"
"gitea.thegux.fr/hmdeploy/connection"
"gitea.thegux.fr/hmdeploy/docker"
"gitea.thegux.fr/hmdeploy/models"
)
func addToArchive(tw *tar.Writer, filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return err
}
header, err := tar.FileInfoHeader(info, info.Name())
if err != nil {
return err
}
header.Name = filepath.Base(file.Name())
if err := tw.WriteHeader(header); err != nil {
return err
}
_, err = io.Copy(tw, file)
return err
}
var (
ErrSwarmDeployerArchive = errors.New("unable to generate archive")
)
type SwarmDeployer struct {
ctx context.Context
fnCancel context.CancelFunc
conn connection.IConnection
dcli docker.IClient
project *models.Project
}
var _ IDeployer = (*SwarmDeployer)(nil)
func NewSwarmDeployer(ctx context.Context, dockerClient docker.IClient, netInfo *models.HMNetInfo, project *models.Project) (SwarmDeployer, error) {
var sm SwarmDeployer
conn, err := connection.NewSSHConn(netInfo.IP.String(), netInfo.SSH.User, netInfo.SSH.Port, netInfo.SSH.PrivKey)
if err != nil {
return sm, err
}
ctxChild, fnCancel := context.WithCancel(ctx)
sm.ctx = ctxChild
sm.fnCancel = fnCancel
sm.conn = &conn
sm.dcli = dockerClient
sm.project = project
return sm, nil
}
func (sd *SwarmDeployer) Close() error {
return sd.conn.Close()
}
func (sd *SwarmDeployer) clean() (err error) {
_, err = sd.conn.Execute(fmt.Sprintf("rm -f %s %s *.tar.gz *.tar", models.ComposeFile, models.EnvFile))
return
}
func (sd *SwarmDeployer) createArchive(files ...string) (string, error) {
now := time.Now().UTC()
archivePath := filepath.Join(sd.project.Dir, fmt.Sprintf("%s-%s.tar.gz", sd.project.Name, strings.Replace(now.Format(time.RFC3339), ":", "-", -1)))
file, err := os.Create(archivePath)
if err != nil {
return "", fmt.Errorf("%w, unable to create archive=%s, err=%v", ErrSwarmDeployerArchive, archivePath, err)
}
defer file.Close()
gw := gzip.NewWriter(file)
defer gw.Close()
tw := tar.NewWriter(gw)
defer tw.Close()
for _, f := range files {
if err := addToArchive(tw, f); err != nil {
return "", fmt.Errorf("%w, unable to add file=%s to archive=%s, err=%v", ErrSwarmDeployerArchive, f, archivePath, err)
}
}
return archivePath, nil
}
func (sd *SwarmDeployer) Deploy() error {
defer sd.clean()
filesToArchive := []string{}
if imageName := sd.project.ImageName; imageName != "" {
log.Info().Str("image", imageName).Msg("saving image for transfert...")
tarFile, err := sd.dcli.Save(imageName, sd.project.Dir)
if err != nil {
return err
}
defer os.Remove(tarFile)
log.Info().Str("image", imageName).Str("dir", sd.project.Dir).Msg("image saved successfully")
filesToArchive = append(filesToArchive, tarFile)
}
if envFilePath := sd.project.Deps.EnvFile; envFilePath != "" {
filesToArchive = append(filesToArchive, filepath.Join(sd.project.Dir, filepath.Base(envFilePath)))
log.Info().Msg(".env file added to the archive for deployment")
}
composeFileBase := filepath.Base(sd.project.Deps.ComposeFile)
filesToArchive = append(filesToArchive, filepath.Join(sd.project.Dir, composeFileBase))
archivePath, err := sd.createArchive(filesToArchive...)
if err != nil {
return err
}
defer os.Remove(archivePath)
archiveDestPath := filepath.Base(archivePath)
log.Info().Str("archive", archivePath).Msg("archive built with success, tranfering to swarm for deployment...")
if err := sd.conn.CopyFile(archivePath, archiveDestPath); err != nil {
return err
}
if _, err := sd.conn.Execute(fmt.Sprintf("tar xzvf %s", archiveDestPath)); err != nil {
return err
}
log.Info().Str("project", sd.project.Name).Msg("deploying project...")
if _, err := sd.conn.Execute(fmt.Sprintf("docker stack deploy -c %s %s", composeFileBase, sd.project.Name)); err != nil {
return err
}
return nil
}