diff --git a/connection/ssh_connection.go b/connection/ssh_connection.go index a981cf3..07213ba 100644 --- a/connection/ssh_connection.go +++ b/connection/ssh_connection.go @@ -28,7 +28,7 @@ var ( ErrSSHSession = errors.New("unable to open a new session") ErrSSHReadPrivateKey = errors.New("unable to read private key") ErrSSHParsePrivateKey = errors.New("unable to read private key") - ErrSSHExecute = errors.New("unable") + ErrSSHExecute = errors.New("unable to execute command") ) func NewSSHConn(addr, user string, port int, privkey string) (SSHConn, error) { diff --git a/deployers/swarm.go b/deployers/swarm.go index 90223e4..adc1ca1 100644 --- a/deployers/swarm.go +++ b/deployers/swarm.go @@ -1,15 +1,52 @@ 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" - "github.com/rs/zerolog/log" +) + +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 { @@ -49,13 +86,40 @@ func (sd *SwarmDeployer) Close() error { } func (sd *SwarmDeployer) clean() (err error) { - _, err = sd.conn.Execute(fmt.Sprintf("rm -f %s %s", models.ComposeFile, models.EnvFile)) + _, 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...") @@ -64,32 +128,37 @@ func (sd *SwarmDeployer) Deploy() error { return err } - log.Info().Str("image", imageName).Str("dir", sd.project.Dir).Msg("image saved successfully") - defer os.Remove(tarFile) - tarFileBase := filepath.Base(tarFile) - if err := sd.conn.CopyFile(tarFile, tarFileBase); err != nil { - return err - } - - if _, err := sd.conn.Execute(fmt.Sprintf("docker load -i %s && rm %s", tarFileBase, tarFileBase)); err != nil { - return err - } + 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 != "" { - envFileBase := filepath.Base(envFilePath) - if err := sd.conn.CopyFile(filepath.Join(sd.project.Dir, envFileBase), envFileBase); err != nil { - return err - } + 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) - if err := sd.conn.CopyFile(filepath.Join(sd.project.Dir, composeFileBase), composeFileBase); err != nil { + 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 }