213 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			213 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package deployers
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 
 | |
| 	"gitea.thegux.fr/hmdeploy/connection"
 | |
| 	"gitea.thegux.fr/hmdeploy/docker"
 | |
| 	"gitea.thegux.fr/hmdeploy/models"
 | |
| 	"gitea.thegux.fr/hmdeploy/utils"
 | |
| 	"github.com/rs/zerolog/log"
 | |
| )
 | |
| 
 | |
| var ErrSwarmDeployerNoArchive = errors.New("no archive found to be deployed")
 | |
| 
 | |
| // SwarmDeployer handles the deployment of a Docker service on the swarm instance.
 | |
| type SwarmDeployer struct {
 | |
| 	*deployer
 | |
| 	conn        connection.IConnection
 | |
| 	dcli        docker.IClient
 | |
| 	archivePath string
 | |
| }
 | |
| 
 | |
| var _ IDeployer = (*SwarmDeployer)(nil)
 | |
| 
 | |
| func NewSwarmDeployer(
 | |
| 	ctx context.Context,
 | |
| 	project *models.Project,
 | |
| 	netInfo *models.HMNetInfo,
 | |
| 	dockerClient docker.IClient,
 | |
| ) (SwarmDeployer, error) {
 | |
| 	var sd SwarmDeployer
 | |
| 
 | |
| 	conn, err := connection.NewSSHConn(
 | |
| 		netInfo.IP.String(),
 | |
| 		netInfo.SSH.User,
 | |
| 		netInfo.SSH.Port,
 | |
| 		netInfo.SSH.PrivKey,
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		return sd, err
 | |
| 	}
 | |
| 
 | |
| 	sd.conn = &conn
 | |
| 	sd.dcli = dockerClient
 | |
| 	sd.deployer = newDeployer(ctx, Swarm, project)
 | |
| 
 | |
| 	return sd, nil
 | |
| }
 | |
| 
 | |
| func (sd *SwarmDeployer) close() error {
 | |
| 	return sd.conn.Close()
 | |
| }
 | |
| 
 | |
| func (sd *SwarmDeployer) clean() (err error) {
 | |
| 	if err = os.Remove(sd.archivePath); err != nil {
 | |
| 		log.Err(err).Str("archive", sd.archivePath).Msg("unable to clean local swarm archive file")
 | |
| 	}
 | |
| 	_, err = sd.conn.Execute(
 | |
| 		fmt.Sprintf("rm -f %s %s *.tar.gz *.tar", models.ComposeFile, models.EnvFile),
 | |
| 	)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (sd *SwarmDeployer) Clear() error {
 | |
| 	log.Debug().Msg("clearing swarm deployment...")
 | |
| 
 | |
| 	if err := sd.clean(); err != nil {
 | |
| 		log.Err(err).Msg("unable to clean swarm conf remotly")
 | |
| 	}
 | |
| 
 | |
| 	if err := sd.close(); err != nil {
 | |
| 		log.Err(err).Msg("unable to close swarm conn")
 | |
| 	}
 | |
| 
 | |
| 	log.Debug().Msg("clear swarm deployment done")
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Build builds the archive with mandatory files to deploy a swarm service.
 | |
| //
 | |
| // After the build, the path of the local archive built is set in
 | |
| // the `archivePath` field.
 | |
| func (sd *SwarmDeployer) Build() error {
 | |
| 	sd.processing.Store(true)
 | |
| 	defer sd.processing.Store(false)
 | |
| 
 | |
| 	select {
 | |
| 	case <-sd.ctx.Done():
 | |
| 		sd.setDone(nil)
 | |
| 		return fmt.Errorf("%w, swarm project build skipped", ErrContextDone)
 | |
| 	default:
 | |
| 	}
 | |
| 
 | |
| 	log.Info().Msg("building swarm archive for deployment...")
 | |
| 
 | |
| 	filesToArchive := []string{}
 | |
| 	for idx := range sd.project.ImageNames {
 | |
| 		tarFile, err := sd.dcli.Save(sd.project.ImageNames[idx], sd.project.Dir)
 | |
| 		if err != nil {
 | |
| 			sd.setDone(err)
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		defer os.Remove(tarFile) //nolint: errcheck // defered
 | |
| 
 | |
| 		// copy the file directly instead of adding it in the tar archive
 | |
| 		log.Info().Str("image", tarFile).Msg("Transferring image...")
 | |
| 		if err := sd.conn.CopyFile(tarFile, filepath.Base(tarFile)); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		log.Info().Str("image", tarFile).Msg("image transferred with success")
 | |
| 	}
 | |
| 
 | |
| 	if envFilePath := sd.project.Deps.EnvFile; envFilePath != "" {
 | |
| 		filesToArchive = append(
 | |
| 			filesToArchive,
 | |
| 			envFilePath,
 | |
| 		)
 | |
| 		log.Info().Msg(".env file added to the archive for deployment")
 | |
| 	}
 | |
| 
 | |
| 	filesToArchive = append(
 | |
| 		filesToArchive,
 | |
| 		sd.project.Deps.ComposeFile,
 | |
| 	)
 | |
| 
 | |
| 	archivePath, err := utils.CreateArchive(
 | |
| 		sd.project.Dir,
 | |
| 		fmt.Sprintf("%s-%s", sd.project.Name, "swarm"),
 | |
| 		filesToArchive...)
 | |
| 	if err != nil {
 | |
| 		sd.setDone(err)
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	sd.archivePath = archivePath
 | |
| 
 | |
| 	log.Info().Str("archive", archivePath).Msg("swarm archive built")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (sd *SwarmDeployer) Deploy() error {
 | |
| 	sd.processing.Store(true)
 | |
| 	defer sd.processing.Store(false)
 | |
| 
 | |
| 	select {
 | |
| 	case <-sd.ctx.Done():
 | |
| 		sd.setDone(nil)
 | |
| 		return fmt.Errorf("%w, swarm deployment skipped", ErrContextDone)
 | |
| 	default:
 | |
| 	}
 | |
| 
 | |
| 	if sd.archivePath == "" {
 | |
| 		sd.setDone(ErrSwarmDeployerNoArchive)
 | |
| 		return ErrSwarmDeployerNoArchive
 | |
| 	}
 | |
| 
 | |
| 	log.Info().Str("archive", sd.archivePath).Msg("deploying archive to swarm...")
 | |
| 
 | |
| 	for idx := range sd.project.ImageNames {
 | |
| 		if _, err := sd.conn.Execute("docker image load -i " + sd.project.ImageNames[idx] + ".tar"); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	archiveDestPath := filepath.Base(sd.archivePath)
 | |
| 	log.Info().
 | |
| 		Str("archive", sd.archivePath).
 | |
| 		Msg("archive built with success, tranferring to swarm for deployment...")
 | |
| 	if err := sd.conn.CopyFile(sd.archivePath, archiveDestPath); err != nil {
 | |
| 		sd.setDone(err)
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if _, err := sd.conn.Execute(fmt.Sprintf("tar xzvf %s", archiveDestPath)); err != nil {
 | |
| 		sd.setDone(err)
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	log.Info().Str("project", sd.project.Name).Msg("deploying swarm project...")
 | |
| 	composeFileBase := filepath.Base(sd.project.Deps.ComposeFile)
 | |
| 	if _, err := sd.conn.Execute(fmt.Sprintf("docker stack deploy -c %s %s --with-registry-auth", composeFileBase, sd.project.Name)); err != nil {
 | |
| 		sd.setDone(err)
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	log.Info().Msg("swarm deployment done with success")
 | |
| 
 | |
| 	sd.setDone(nil)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (sd *SwarmDeployer) Destroy() error {
 | |
| 	sd.processing.Store(true)
 | |
| 	defer sd.processing.Store(false)
 | |
| 
 | |
| 	log.Info().Str("project", sd.project.Name).Msg("destroying swarm project...")
 | |
| 	if _, err := sd.conn.Execute(fmt.Sprintf("docker stack rm %s", sd.project.Name)); err != nil {
 | |
| 		sd.setDone(err)
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	log.Info().Msg("swarm undeployment done with success")
 | |
| 
 | |
| 	sd.setDone(nil)
 | |
| 	return nil
 | |
| }
 | 
