containerized-data-importer/tests/utils/fileConversion.go
Idan Shaby 3d9a42629e Support archived content import
This patch adds the ability to import and extract an archived content
from a tar file directly onto the target PVC.
That is, a tar file is expanded while keeping file ownership,
permissions, and tree structure identical to the source.

Signed-off-by: Idan Shaby <ishaby@redhat.com>
2018-11-28 09:41:10 +02:00

195 lines
5.3 KiB
Go

package utils
import (
"archive/tar"
"compress/gzip"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/ulikunitz/xz"
"kubevirt.io/containerized-data-importer/pkg/image"
)
var formatTable = map[string]func(string, string) (string, error){
image.ExtGz: toGz,
image.ExtXz: toXz,
image.ExtTar: toTar,
image.ExtQcow2: toQcow2,
"": toNoop,
}
// FormatTestData accepts the path of a single file (srcFile) and attempts to generate an output
// file in the format defined by targetFormats (e.g. ".tar", ".gz" will produce a .tar.gz formatted file). The output file is written to the directory in `tgtDir`.
// returns:
// (string) Path of output file
// (error) Errors that occur during formatting
func FormatTestData(srcFile, tgtDir string, targetFormats ...string) (string, error) {
var err error
for _, tf := range targetFormats {
f, ok := formatTable[tf]
if !ok {
return "", errors.Errorf("format extension %q not recognized", tf)
}
// invoke conversion func
srcFile, err = f(srcFile, tgtDir)
if err != nil {
return "", errors.Wrap(err, "could not format test data")
}
}
return srcFile, nil
}
func toTar(src, tgtDir string) (string, error) {
return ArchiveFiles(src, tgtDir, src)
}
// ArchiveFiles creates a tar file that archives the given source files.
func ArchiveFiles(targetFile, tgtDir string, sourceFilesNames ...string) (string, error) {
tgtFile, tgtPath, _ := createTargetFile(targetFile, tgtDir, image.ExtTar)
defer tgtFile.Close()
w := tar.NewWriter(tgtFile)
defer w.Close()
for _, src := range sourceFilesNames {
srcFile, err := os.Open(src)
if err != nil {
return "", errors.Wrapf(err, "Error opening file %s", src)
}
defer srcFile.Close()
srcFileInfo, err := srcFile.Stat()
if err != nil {
return "", errors.Wrapf(err, "Error stating file %s", src)
}
hdr, err := tar.FileInfoHeader(srcFileInfo, "")
if err != nil {
return "", errors.Wrapf(err, "Error generating tar file header for %s", src)
}
err = w.WriteHeader(hdr)
if err != nil {
return "", errors.Wrapf(err, "Error writing tar header to %s", tgtPath)
}
_, err = io.Copy(w, srcFile)
if err != nil {
return "", errors.Wrapf(err, "Error writing to file %s", tgtPath)
}
}
return tgtPath, nil
}
func toGz(src, tgtDir string) (string, error) {
tgtFile, tgtPath, _ := createTargetFile(src, tgtDir, image.ExtGz)
defer tgtFile.Close()
w := gzip.NewWriter(tgtFile)
defer w.Close()
srcFile, err := os.Open(src)
if err != nil {
return "", errors.Wrapf(err, "Error opening file %s", src)
}
defer srcFile.Close()
_, err = io.Copy(w, srcFile)
if err != nil {
return "", errors.Wrapf(err, "Error writing to file %s", tgtPath)
}
return tgtPath, nil
}
func toXz(src, tgtDir string) (string, error) {
tgtFile, tgtPath, _ := createTargetFile(src, tgtDir, image.ExtXz)
defer tgtFile.Close()
w, err := xz.NewWriter(tgtFile)
if err != nil {
return "", errors.Wrapf(err, "Error getting xz writer for file %s", tgtPath)
}
defer w.Close()
srcFile, err := os.Open(src)
if err != nil {
return "", errors.Wrapf(err, "Error opening file %s", src)
}
defer srcFile.Close()
_, err = io.Copy(w, srcFile)
if err != nil {
return "", errors.Wrapf(err, "Error writing to file %s", tgtPath)
}
return tgtPath, nil
}
func toQcow2(srcfile, tgtDir string) (string, error) {
base := strings.TrimSuffix(filepath.Base(srcfile), ".iso")
tgt := filepath.Join(tgtDir, base+image.ExtQcow2)
args := []string{"convert", "-f", "raw", "-O", "qcow2", srcfile, tgt}
if err := doCmdAndVerifyFile(tgt, "qemu-img", args...); err != nil {
return "", err
}
return tgt, nil
}
func toNoop(src, tgtDir string) (string, error) {
return copyIfNotPresent(src, tgtDir)
}
func doCmdAndVerifyFile(tgt, cmd string, args ...string) error {
if err := doCmd(cmd, args...); err != nil {
return err
}
if _, err := os.Stat(tgt); err != nil {
return errors.Wrapf(err, "Failed to stat file %q", tgt)
}
return nil
}
func doCmd(osCmd string, osArgs ...string) error {
cmd := exec.Command(osCmd, osArgs...)
cout, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrapf(err, "OS command `%s %v` errored: %v\nStdout/Stderr: %s", osCmd, strings.Join(osArgs, " "), err, string(cout))
}
return nil
}
// copyIfNotPresent checks for the src file in the tgtDir. If it is not there, it attempts to copy it from src to tgtdir.
// If a copy is performed, the path to the copy is returned.
// If the file already exists, the original src string is returned.
func copyIfNotPresent(src, tgtDir string) (string, error) {
ret := filepath.Join(tgtDir, filepath.Base(src))
_, err := os.Stat(ret)
if err != nil && !os.IsNotExist(err) {
return "", errors.Wrap(err, "Unexpected error stating file")
}
if os.IsNotExist(err) {
if err = doCmd("cp", src, ret); err != nil {
return "", err
}
}
return ret, nil
}
// createTargetFile is a simple helper to create a file with the provided extension in the target directory.
// returns a pointer to the new file, path to the new file, or an error. It is the responsibility of the caller to
// close the file.
func createTargetFile(src, tgtDir, ext string) (*os.File, string, error) {
tgt := filepath.Join(tgtDir, filepath.Base(src)+ext)
tgtFile, err := os.Create(tgt)
if err != nil {
return nil, "", errors.Wrap(err, "Error creating file")
}
return tgtFile, tgt, nil
}