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 dloc docker.IClient drem *docker.RemoteClient archivePath string } var _ IDeployer = (*SwarmDeployer)(nil) func NewSwarmDeployer( ctx context.Context, project *models.Project, netInfo *models.HMNetInfo, dloc docker.IClient, drem *docker.RemoteClient, ) (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, nil } sd.conn = &conn sd.dloc = dloc sd.drem = drem 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.dloc.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...") if err := sd.drem.LoadImages(sd.project.ImageNames...); err != nil { sd.setDone(err) 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.drem.DeployStack(sd.ctx, sd.project.Name, composeFileBase, docker.WithCheckState()); 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.drem.DestroyStack(sd.ctx, sd.project.Name, docker.WithCheckState()); err != nil { sd.setDone(err) return err } log.Info().Msg("swarm undeployment done with success") sd.setDone(nil) return nil }