aenvs/internal/archiver/archiver.go

154 lines
3.5 KiB
Go

package archiver
import (
"archive/tar"
"bytes"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
)
// Create creates a tar.gz archive from the given files relative to rootDir
// Returns: archive reader, archive size, error
func Create(files []string, rootDir string) (io.Reader, int64, error) {
var buf bytes.Buffer
gzWriter := gzip.NewWriter(&buf)
tarWriter := tar.NewWriter(gzWriter)
for _, file := range files {
// Get full path
fullPath := filepath.Join(rootDir, file)
// Open file
f, err := os.Open(fullPath)
if err != nil {
return nil, 0, fmt.Errorf("failed to open file %s: %w", file, err)
}
// Get file info
info, err := f.Stat()
if err != nil {
f.Close()
return nil, 0, fmt.Errorf("failed to stat file %s: %w", file, err)
}
// Create tar header
header := &tar.Header{
Name: file, // Use relative path
Size: info.Size(),
Mode: int64(info.Mode()),
ModTime: info.ModTime(),
}
// Write header
if err := tarWriter.WriteHeader(header); err != nil {
f.Close()
return nil, 0, fmt.Errorf("failed to write tar header for %s: %w", file, err)
}
// Write file content
if _, err := io.Copy(tarWriter, f); err != nil {
f.Close()
return nil, 0, fmt.Errorf("failed to write file content for %s: %w", file, err)
}
f.Close()
}
// Close tar writer
if err := tarWriter.Close(); err != nil {
return nil, 0, fmt.Errorf("failed to close tar writer: %w", err)
}
// Close gzip writer
if err := gzWriter.Close(); err != nil {
return nil, 0, fmt.Errorf("failed to close gzip writer: %w", err)
}
size := int64(buf.Len())
return &buf, size, nil
}
// Extract extracts a tar.gz archive to the destination directory
// Returns: list of extracted files, error
func Extract(archive io.Reader, destDir string) ([]string, error) {
// Create gzip reader
gzReader, err := gzip.NewReader(archive)
if err != nil {
return nil, fmt.Errorf("failed to create gzip reader: %w", err)
}
defer gzReader.Close()
// Create tar reader
tarReader := tar.NewReader(gzReader)
var extractedFiles []string
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("failed to read tar header: %w", err)
}
// Get target path
targetPath := filepath.Join(destDir, header.Name)
// Ensure directory exists
dir := filepath.Dir(targetPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("failed to create directory %s: %w", dir, err)
}
// Create file
f, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(header.Mode))
if err != nil {
return nil, fmt.Errorf("failed to create file %s: %w", targetPath, err)
}
// Copy content
if _, err := io.Copy(f, tarReader); err != nil {
f.Close()
return nil, fmt.Errorf("failed to write file content for %s: %w", targetPath, err)
}
f.Close()
extractedFiles = append(extractedFiles, header.Name)
}
return extractedFiles, nil
}
// List lists files in the archive without extracting
func List(archive io.Reader) ([]string, error) {
// Create gzip reader
gzReader, err := gzip.NewReader(archive)
if err != nil {
return nil, fmt.Errorf("failed to create gzip reader: %w", err)
}
defer gzReader.Close()
// Create tar reader
tarReader := tar.NewReader(gzReader)
var files []string
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("failed to read tar header: %w", err)
}
files = append(files, header.Name)
}
return files, nil
}