mirror of
https://github.com/containers/podman.git
synced 2025-10-18 03:33:32 +08:00
Refactor key machine objects
In #20538, I was asked to consider refactoring the new OCI pull code from within the generic machine directory. This is something I had tried when originally coding it but it became apparent that a much larger refactor to prevent circular deps was needed. Because I did not want to pollute the initial PR with that refactor, I asked for the PR to merge first. This is the refactor that needed to be done. Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
91
pkg/machine/compression/compression_test.go
Normal file
91
pkg/machine/compression/compression_test.go
Normal file
@ -0,0 +1,91 @@
|
||||
package compression
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_compressionFromFile(t *testing.T) {
|
||||
type args struct {
|
||||
path string
|
||||
}
|
||||
var tests = []struct {
|
||||
name string
|
||||
args args
|
||||
want ImageCompression
|
||||
}{
|
||||
{
|
||||
name: "xz",
|
||||
args: args{
|
||||
path: "/tmp/foo.xz",
|
||||
},
|
||||
want: Xz,
|
||||
},
|
||||
{
|
||||
name: "gzip",
|
||||
args: args{
|
||||
path: "/tmp/foo.gz",
|
||||
},
|
||||
want: Gz,
|
||||
},
|
||||
{
|
||||
name: "bz2",
|
||||
args: args{
|
||||
path: "/tmp/foo.bz2",
|
||||
},
|
||||
want: Bz2,
|
||||
},
|
||||
{
|
||||
name: "default is xz",
|
||||
args: args{
|
||||
path: "/tmp/foo",
|
||||
},
|
||||
want: Xz,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := KindFromFile(tt.args.path); got != tt.want {
|
||||
t.Errorf("KindFromFile() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageCompression_String(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
c ImageCompression
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "xz",
|
||||
c: Xz,
|
||||
want: "xz",
|
||||
},
|
||||
{
|
||||
name: "gz",
|
||||
c: Gz,
|
||||
want: "gz",
|
||||
},
|
||||
{
|
||||
name: "bz2",
|
||||
c: Bz2,
|
||||
want: "bz2",
|
||||
},
|
||||
{
|
||||
name: "zip",
|
||||
c: Zip,
|
||||
want: "zip",
|
||||
},
|
||||
{
|
||||
name: "xz is default",
|
||||
c: 99,
|
||||
want: "xz",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.c.String(); got != tt.want {
|
||||
t.Errorf("String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
36
pkg/machine/compression/config.go
Normal file
36
pkg/machine/compression/config.go
Normal file
@ -0,0 +1,36 @@
|
||||
package compression
|
||||
|
||||
import "strings"
|
||||
|
||||
type ImageCompression int64
|
||||
|
||||
const (
|
||||
Xz ImageCompression = iota
|
||||
Zip
|
||||
Gz
|
||||
Bz2
|
||||
)
|
||||
|
||||
func KindFromFile(path string) ImageCompression {
|
||||
switch {
|
||||
case strings.HasSuffix(path, Bz2.String()):
|
||||
return Bz2
|
||||
case strings.HasSuffix(path, Gz.String()):
|
||||
return Gz
|
||||
case strings.HasSuffix(path, Zip.String()):
|
||||
return Zip
|
||||
}
|
||||
return Xz
|
||||
}
|
||||
|
||||
func (c ImageCompression) String() string {
|
||||
switch c {
|
||||
case Gz:
|
||||
return "gz"
|
||||
case Zip:
|
||||
return "zip"
|
||||
case Bz2:
|
||||
return "bz2"
|
||||
}
|
||||
return "xz"
|
||||
}
|
184
pkg/machine/compression/decompress.go
Normal file
184
pkg/machine/compression/decompress.go
Normal file
@ -0,0 +1,184 @@
|
||||
package compression
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/pkg/compression"
|
||||
"github.com/containers/podman/v4/pkg/machine/define"
|
||||
"github.com/containers/podman/v4/utils"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/ulikunitz/xz"
|
||||
)
|
||||
|
||||
func Decompress(localPath *define.VMFile, uncompressedPath string) error {
|
||||
var isZip bool
|
||||
uncompressedFileWriter, err := os.OpenFile(uncompressedPath, os.O_CREATE|os.O_RDWR, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sourceFile, err := localPath.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasSuffix(localPath.GetPath(), ".zip") {
|
||||
isZip = true
|
||||
}
|
||||
prefix := "Copying uncompressed file"
|
||||
compressionType := archive.DetectCompression(sourceFile)
|
||||
if compressionType != archive.Uncompressed || isZip {
|
||||
prefix = "Extracting compressed file"
|
||||
}
|
||||
prefix += ": " + filepath.Base(uncompressedPath)
|
||||
if compressionType == archive.Xz {
|
||||
return decompressXZ(prefix, localPath.GetPath(), uncompressedFileWriter)
|
||||
}
|
||||
if isZip && runtime.GOOS == "windows" {
|
||||
return decompressZip(prefix, localPath.GetPath(), uncompressedFileWriter)
|
||||
}
|
||||
return decompressEverythingElse(prefix, localPath.GetPath(), uncompressedFileWriter)
|
||||
}
|
||||
|
||||
// Will error out if file without .Xz already exists
|
||||
// Maybe extracting then renaming is a good idea here..
|
||||
// depends on Xz: not pre-installed on mac, so it becomes a brew dependency
|
||||
func decompressXZ(prefix string, src string, output io.WriteCloser) error {
|
||||
var read io.Reader
|
||||
var cmd *exec.Cmd
|
||||
|
||||
stat, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
p, bar := utils.ProgressBar(prefix, stat.Size(), prefix+": done")
|
||||
proxyReader := bar.ProxyReader(file)
|
||||
defer func() {
|
||||
if err := proxyReader.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Prefer Xz utils for fastest performance, fallback to go xi2 impl
|
||||
if _, err := exec.LookPath("xz"); err == nil {
|
||||
cmd = exec.Command("xz", "-d", "-c")
|
||||
cmd.Stdin = proxyReader
|
||||
read, err = cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Stderr = os.Stderr
|
||||
} else {
|
||||
// This XZ implementation is reliant on buffering. It is also 3x+ slower than XZ utils.
|
||||
// Consider replacing with a faster implementation (e.g. xi2) if podman machine is
|
||||
// updated with a larger image for the distribution base.
|
||||
buf := bufio.NewReader(proxyReader)
|
||||
read, err = xz.NewReader(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
if _, err := io.Copy(output, read); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
output.Close()
|
||||
done <- true
|
||||
}()
|
||||
|
||||
if cmd != nil {
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Wait()
|
||||
return cmd.Wait()
|
||||
}
|
||||
<-done
|
||||
p.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func decompressEverythingElse(prefix string, src string, output io.WriteCloser) error {
|
||||
stat, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p, bar := utils.ProgressBar(prefix, stat.Size(), prefix+": done")
|
||||
proxyReader := bar.ProxyReader(f)
|
||||
defer func() {
|
||||
if err := proxyReader.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
uncompressStream, _, err := compression.AutoDecompress(proxyReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := uncompressStream.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if err := output.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = io.Copy(output, uncompressStream)
|
||||
p.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
func decompressZip(prefix string, src string, output io.WriteCloser) error {
|
||||
zipReader, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(zipReader.File) != 1 {
|
||||
return errors.New("machine image files should consist of a single compressed file")
|
||||
}
|
||||
f, err := zipReader.File[0].Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
if err := output.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
size := int64(zipReader.File[0].CompressedSize64)
|
||||
p, bar := utils.ProgressBar(prefix, size, prefix+": done")
|
||||
proxyReader := bar.ProxyReader(f)
|
||||
defer func() {
|
||||
if err := proxyReader.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
_, err = io.Copy(output, proxyReader)
|
||||
p.Wait()
|
||||
return err
|
||||
}
|
Reference in New Issue
Block a user