makeshift/internal/archive/archive.go

220 lines
4.9 KiB
Go

package archive
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
makeshift "git.towk2.me/towk/makeshift/pkg"
)
func Create(filenames []string, buf io.Writer, hooks []makeshift.Hook) error {
// Create new Writers for gzip and tar
// These writers are chained. Writing to the tar writer will
// write to the gzip writer which in turn will write to
// the "buf" writer
gw := gzip.NewWriter(buf)
defer gw.Close()
tw := tar.NewWriter(gw)
defer tw.Close()
// Iterate over files and add them to the tar archive
for _, filename := range filenames {
err := addToArchive(tw, filename, hooks)
if err != nil {
return err
}
}
return nil
}
func Expand(tarname, xpath string) error {
tarfile, err := os.Open(tarname)
if err != nil {
return err
}
defer tarfile.Close()
// absPath, err := filepath.Abs(xpath)
// if err != nil {
// return err
// }
tr := tar.NewReader(tarfile)
if strings.HasSuffix(tarname, ".gz") {
gz, err := gzip.NewReader(tarfile)
if err != nil {
return fmt.Errorf("failed to create new gzip reader: %v", err)
}
defer gz.Close()
tr = tar.NewReader(gz)
}
// untar each segment
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("failed to get next tar header: %v", err)
}
// determine proper file path info
var (
fileinfo = header.FileInfo()
filename = header.Name
file *os.File
abspath string
dirpath string
)
// absFileName := filepath.Join(absPath, filename) // if a dir, create it, then go to next segment
if fileinfo.Mode().IsDir() {
if err := os.MkdirAll(filename, 0o755); err != nil {
return fmt.Errorf("failed to make directory '%s': %v", filename, err)
}
continue
}
dirpath = filepath.Dir(filename)
if err = os.MkdirAll(dirpath, 0o777); err != nil {
return fmt.Errorf("failed to make directory '%s': %v", err)
}
// create new file with original file mode
abspath, err = filepath.Abs(filename)
if err != nil {
return fmt.Errorf("failed to get absolute path: %v", err)
}
file, err = os.OpenFile(
abspath,
os.O_RDWR|os.O_CREATE|os.O_TRUNC,
fileinfo.Mode().Perm(),
)
if err != nil {
return fmt.Errorf("failed to open file: %v", err)
}
// fmt.Printf("x %s\n", filename)
// copy the contents to the new file
n, err := io.Copy(file, tr)
if err != nil {
return fmt.Errorf("failed to copy file: %v", err)
}
if err = file.Close(); err != nil {
return fmt.Errorf("failed to close file: %v", err)
}
if n != fileinfo.Size() {
return fmt.Errorf("wrote %d, want %d", n, fileinfo.Size())
}
}
return nil
}
func addToArchive(tw *tar.Writer, filename string, hooks []makeshift.Hook) error {
var (
tempfile = fmt.Sprintf("%s.tmp", filename)
file *os.File
contents []byte
data any
err error
)
// run pre-hooks to modify the contents of the file
// before archiving using plugins
for _, hook := range hooks {
// set the file in the data store before running hook
contents, err = os.ReadFile(filename)
if err != nil {
return fmt.Errorf("failed to read '%s' to download: %v", filename, err)
}
hook.Data.Set("file", contents)
err = hook.Init()
if err != nil {
return err
}
err = hook.Run()
if err != nil {
return err
}
err = hook.Cleanup()
if err != nil {
return err
}
// create temporary file to use to add to archive
hook = hooks[len(hooks)-1]
data, err = hook.Data.Get("out")
if err != nil {
return fmt.Errorf("failed to get output data from '%s' plugin: %v", hook.Plugin.Name(), err)
}
err = os.WriteFile(tempfile, data.([]byte), 0o777)
if err != nil {
return fmt.Errorf("failed to write temporary file: %v", err)
}
}
// use original file if no hooks to write archive
if len(hooks) == 0 {
file, err = os.Open(filename)
} else {
file, err = os.Open(tempfile)
}
if err != nil {
return fmt.Errorf("failed to open archive file: %v", err)
}
defer file.Close()
// get FileInfo for file size, mode, etc.
info, err := file.Stat()
if err != nil {
return err
}
// skip file if it's a directory
if info.IsDir() {
return nil
}
// create a tar Header from the FileInfo data
header, err := tar.FileInfoHeader(info, info.Name())
if err != nil {
return fmt.Errorf("failed to create FileInfoHeader: %v", err)
}
// use full path as name (FileInfoHeader only takes the basename) to
// preserve directory structure
// see for more info: https://golang.org/src/archive/tar/common.go?#L626
header.Name = filename
// Write file header to the tar archive
err = tw.WriteHeader(header)
if err != nil {
return err
}
// copy file content to tar archive
if len(hooks) == 0 {
_, err = io.Copy(tw, file)
} else {
_, err = io.Copy(tw, strings.NewReader(string(data.([]byte))))
}
if err != nil {
return err
}
// delete the temporary file since we're done with it
if len(hooks) != 0 {
err = os.Remove(tempfile)
if err != nil {
return err
}
}
return nil
}