mirror of
https://github.com/kubevirt/containerized-data-importer.git
synced 2025-06-03 06:30:22 +00:00

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>
195 lines
5.3 KiB
Go
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
|
|
}
|