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 }