package deployers import ( "context" "fmt" "os" "path/filepath" "github.com/rs/zerolog/log" "gitea.thegux.fr/hmdeploy/connection" "gitea.thegux.fr/hmdeploy/docker" "gitea.thegux.fr/hmdeploy/models" "gitea.thegux.fr/hmdeploy/utils" ) type SwarmDeployer struct { ctx context.Context conn connection.IConnection dcli docker.IClient project *models.Project archivePath string chDone chan struct{} } var _ IDeployer = (*SwarmDeployer)(nil) func NewSwarmDeployer(ctx context.Context, dockerClient docker.IClient, netInfo *models.HMNetInfo, project *models.Project) (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.ctx = ctx sd.conn = &conn sd.dcli = dockerClient sd.project = project sd.chDone = make(chan struct{}, 5) return sd, nil } func (sd *SwarmDeployer) close() error { return sd.conn.Close() } func (sd *SwarmDeployer) clean() (err error) { defer os.Remove(sd.archivePath) _, err = sd.conn.Execute(fmt.Sprintf("rm -f %s %s *.tar.gz *.tar", models.ComposeFile, models.EnvFile)) return } func (sd *SwarmDeployer) setDone() { sd.chDone <- struct{}{} } func (sd *SwarmDeployer) Done() <-chan struct{} { chDone := make(chan struct{}) go func() { for { select { case <-sd.chDone: chDone <- struct{}{} return case <-sd.ctx.Done(): chDone <- struct{}{} return } } }() return chDone } 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 } func (sd *SwarmDeployer) Build() error { select { case <-sd.ctx.Done(): sd.setDone() return fmt.Errorf("%w, swarm project build skipped", ErrContextDone) default: } log.Info().Msg("building swarm archive for deployment...") filesToArchive := []string{} if imageName := sd.project.ImageName; imageName != "" { tarFile, err := sd.dcli.Save(imageName, sd.project.Dir) if err != nil { sd.setDone() return err } defer os.Remove(tarFile) filesToArchive = append(filesToArchive, tarFile) log.Info().Str("image", imageName).Msg("image added to archive") } 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 := utils.CreateArchive(sd.project.Dir, fmt.Sprintf("%s-%s", sd.project.Name, "swarm"), filesToArchive...) if err != nil { sd.setDone() return err } sd.archivePath = archivePath log.Info().Str("archive", archivePath).Msg("swarm archive built") return nil } func (sd *SwarmDeployer) Deploy() error { defer sd.setDone() select { case <-sd.ctx.Done(): return fmt.Errorf("%w, swarm deployment skipped", ErrContextDone) default: } if sd.archivePath == "" { return fmt.Errorf("unable to deploy, no archive to deploy") } log.Info().Str("archive", sd.archivePath).Msg("deploying archive to swarm...") archiveDestPath := filepath.Base(sd.archivePath) log.Info().Str("archive", sd.archivePath).Msg("archive built with success, tranfering to swarm for deployment...") if err := sd.conn.CopyFile(sd.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 swarm project...") composeFileBase := filepath.Base(sd.project.Deps.ComposeFile) if _, err := sd.conn.Execute(fmt.Sprintf("docker stack deploy -c %s %s", composeFileBase, sd.project.Name)); err != nil { return err } log.Info().Msg("swarm deployment done with success") return nil }