archiver/archive.go (104 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.
package archiver
import (
"archive/zip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/joeshaw/multierror"
)
// PackageProperties defines properties describing the package. The structure is used for archiving.
type PackageProperties struct {
Name string
Version string
Path string
}
// ArchivePackage method builds and streams an archive with package content.
func ArchivePackage(w io.Writer, properties PackageProperties) (err error) {
zipWriter := zip.NewWriter(w)
defer func() {
var multiErr multierror.Errors
if err != nil {
multiErr = append(multiErr, err)
}
err = zipWriter.Close()
if err != nil {
multiErr = append(multiErr, fmt.Errorf("closing zip writer failed: %w", err))
}
if multiErr != nil {
err = multiErr.Err()
}
}()
rootDir := fmt.Sprintf("%s-%s", properties.Name, properties.Version)
err = filepath.Walk(properties.Path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relativePath, err := filepath.Rel(properties.Path, path)
if err != nil {
return fmt.Errorf("finding relative path failed (packagePath: %s, path: %s): %w", properties.Path, path, err)
}
if relativePath == "." {
return nil
}
header, err := buildArchiveHeader(info, filepath.Join(rootDir, relativePath))
if err != nil {
return fmt.Errorf("building archive header failed (path: %s): %w", relativePath, err)
}
w, err = zipWriter.CreateHeader(header)
if err != nil {
return fmt.Errorf("writing header failed (path: %s): %w", relativePath, err)
}
if !info.IsDir() {
err = writeFileContentToArchive(path, w)
if err != nil {
return fmt.Errorf("archiving file content failed (path: %s): %w", path, err)
}
}
return nil
})
if err != nil {
return fmt.Errorf("processing package path '%s' failed: %w", properties.Path, err)
}
err = zipWriter.Flush()
if err != nil {
return fmt.Errorf("flushing zip writer failed: %w", err)
}
return nil
}
func buildArchiveHeader(info os.FileInfo, relativePath string) (*zip.FileHeader, error) {
header, err := zip.FileInfoHeader(info)
if err != nil {
return nil, fmt.Errorf("reading file info header failed (info: %s): %w", info.Name(), err)
}
header.Method = zip.Deflate
header.Name = relativePath
if info.IsDir() && !strings.HasSuffix(header.Name, "/") {
header.Name = header.Name + "/"
}
return header, nil
}
func writeFileContentToArchive(path string, writer io.Writer) (err error) {
var f *os.File
f, err = os.Open(path)
if err != nil {
return fmt.Errorf("opening file failed (path: %s): %w", path, err)
}
defer func() {
var multiErr multierror.Errors
if err != nil {
multiErr = append(multiErr, err)
}
err = f.Close()
if err != nil {
multiErr = append(multiErr, fmt.Errorf("closing file failed (path: %s): %w", path, err))
}
if multiErr != nil {
err = multiErr.Err()
}
}()
_, err = io.Copy(writer, f)
if err != nil {
return fmt.Errorf("copying file content failed (path: %s): %w", path, err)
}
return nil
}