Merge pull request #9872 from baude/vmaltimage
podman machine init user input
This commit is contained in:
commit
5e28b35aa5
|
@ -2,17 +2,13 @@ package machine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
url2 "net/url"
|
url2 "net/url"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/storage/pkg/archive"
|
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// These should eventually be moved into machine/qemu as
|
// These should eventually be moved into machine/qemu as
|
||||||
|
@ -75,41 +71,7 @@ func (f FcosDownload) DownloadImage() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uncompressedFileWriter, err := os.OpenFile(f.getLocalUncompressedName(), os.O_CREATE|os.O_RDWR, 0600)
|
return Decompress(f.LocalPath, f.getLocalUncompressedName())
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sourceFile, err := ioutil.ReadFile(f.LocalPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
compressionType := archive.DetectCompression(sourceFile)
|
|
||||||
f.CompressionType = compressionType.Extension()
|
|
||||||
|
|
||||||
switch f.CompressionType {
|
|
||||||
case "tar.xz":
|
|
||||||
return decompressXZ(f.LocalPath, uncompressedFileWriter)
|
|
||||||
default:
|
|
||||||
// File seems to be uncompressed, make a copy
|
|
||||||
if err := copyFile(f.LocalPath, uncompressedFileWriter); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFile(src string, dest *os.File) error {
|
|
||||||
source, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := source.Close(); err != nil {
|
|
||||||
logrus.Error(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
_, err = io.Copy(dest, source)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f FcosDownload) Get() *Download {
|
func (f FcosDownload) Get() *Download {
|
||||||
|
|
|
@ -3,17 +3,94 @@ package machine
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
url2 "net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containers/image/v5/pkg/compression"
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/vbauerster/mpb/v6"
|
"github.com/vbauerster/mpb/v6"
|
||||||
"github.com/vbauerster/mpb/v6/decor"
|
"github.com/vbauerster/mpb/v6/decor"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GenericDownload is used when a user provides a URL
|
||||||
|
// or path for an image
|
||||||
|
type GenericDownload struct {
|
||||||
|
Download
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGenericDownloader is used when the disk image is provided by the user
|
||||||
|
func NewGenericDownloader(vmType, vmName, pullPath string) (DistributionDownload, error) {
|
||||||
|
var (
|
||||||
|
imageName string
|
||||||
|
)
|
||||||
|
dataDir, err := GetDataDir(vmType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dl := Download{}
|
||||||
|
// Is pullpath a file or url?
|
||||||
|
getURL, err := url2.Parse(pullPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(getURL.Scheme) > 0 {
|
||||||
|
urlSplit := strings.Split(pullPath, "/")
|
||||||
|
imageName = urlSplit[len(urlSplit)-1]
|
||||||
|
dl.LocalUncompressedFile = filepath.Join(dataDir, imageName)
|
||||||
|
dl.URL = getURL
|
||||||
|
dl.LocalPath = filepath.Join(dataDir, imageName)
|
||||||
|
} else {
|
||||||
|
// Dealing with FilePath
|
||||||
|
imageName = filepath.Base(pullPath)
|
||||||
|
dl.LocalUncompressedFile = filepath.Join(dataDir, imageName)
|
||||||
|
dl.LocalPath = pullPath
|
||||||
|
}
|
||||||
|
dl.VMName = vmName
|
||||||
|
dl.ImageName = imageName
|
||||||
|
// The download needs to be pulled into the datadir
|
||||||
|
|
||||||
|
gd := GenericDownload{Download: dl}
|
||||||
|
gd.LocalUncompressedFile = gd.getLocalUncompressedName()
|
||||||
|
return gd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GenericDownload) getLocalUncompressedName() string {
|
||||||
|
var (
|
||||||
|
extension string
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(g.LocalPath, ".bz2"):
|
||||||
|
extension = ".bz2"
|
||||||
|
case strings.HasSuffix(g.LocalPath, ".gz"):
|
||||||
|
extension = ".gz"
|
||||||
|
case strings.HasSuffix(g.LocalPath, ".xz"):
|
||||||
|
extension = ".xz"
|
||||||
|
}
|
||||||
|
uncompressedFilename := filepath.Join(filepath.Dir(g.LocalUncompressedFile), g.VMName+"_"+g.ImageName)
|
||||||
|
return strings.TrimSuffix(uncompressedFilename, extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GenericDownload) DownloadImage() error {
|
||||||
|
// If we have a URL for this "downloader", we now pull it
|
||||||
|
if g.URL != nil {
|
||||||
|
if err := DownloadVMImage(g.URL, g.LocalPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Decompress(g.LocalPath, g.getLocalUncompressedName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GenericDownload) Get() *Download {
|
||||||
|
return &g.Download
|
||||||
|
}
|
||||||
|
|
||||||
// DownloadVMImage downloads a VM image from url to given path
|
// DownloadVMImage downloads a VM image from url to given path
|
||||||
// with download status
|
// with download status
|
||||||
func DownloadVMImage(downloadURL fmt.Stringer, localImagePath string) error {
|
func DownloadVMImage(downloadURL fmt.Stringer, localImagePath string) error {
|
||||||
|
@ -75,6 +152,22 @@ func DownloadVMImage(downloadURL fmt.Stringer, localImagePath string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Decompress(localPath, uncompressedPath string) error {
|
||||||
|
uncompressedFileWriter, err := os.OpenFile(uncompressedPath, os.O_CREATE|os.O_RDWR, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sourceFile, err := ioutil.ReadFile(localPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if compressionType := archive.DetectCompression(sourceFile); compressionType.Extension() == "tar.xz" {
|
||||||
|
return decompressXZ(localPath, uncompressedFileWriter)
|
||||||
|
}
|
||||||
|
return decompressEverythingElse(localPath, uncompressedFileWriter)
|
||||||
|
}
|
||||||
|
|
||||||
// Will error out if file without .xz already exists
|
// Will error out if file without .xz already exists
|
||||||
// Maybe extracting then renameing is a good idea here..
|
// Maybe extracting then renameing is a good idea here..
|
||||||
// depends on xz: not pre-installed on mac, so it becomes a brew dependecy
|
// depends on xz: not pre-installed on mac, so it becomes a brew dependecy
|
||||||
|
@ -95,3 +188,23 @@ func decompressXZ(src string, output io.Writer) error {
|
||||||
}()
|
}()
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decompressEverythingElse(src string, output io.Writer) error {
|
||||||
|
fmt.Println("Extracting compressed file")
|
||||||
|
f, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uncompressStream, _, err := compression.AutoDecompress(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := uncompressStream.Close(); err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err = io.Copy(output, uncompressStream)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -12,9 +12,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containers/podman/v3/utils"
|
|
||||||
|
|
||||||
"github.com/containers/podman/v3/pkg/machine"
|
"github.com/containers/podman/v3/pkg/machine"
|
||||||
|
"github.com/containers/podman/v3/utils"
|
||||||
"github.com/containers/storage/pkg/homedir"
|
"github.com/containers/storage/pkg/homedir"
|
||||||
"github.com/digitalocean/go-qemu/qmp"
|
"github.com/digitalocean/go-qemu/qmp"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -135,15 +134,29 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
|
||||||
jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
|
jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
|
||||||
v.IdentityPath = filepath.Join(sshDir, v.Name)
|
v.IdentityPath = filepath.Join(sshDir, v.Name)
|
||||||
|
|
||||||
dd, err := machine.NewFcosDownloader(vmtype, v.Name)
|
// The user has provided an alternate image which can be a file path
|
||||||
if err != nil {
|
// or URL.
|
||||||
return err
|
if len(opts.ImagePath) > 0 {
|
||||||
|
g, err := machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.ImagePath = g.Get().LocalUncompressedFile
|
||||||
|
if err := g.DownloadImage(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Get the image as usual
|
||||||
|
dd, err := machine.NewFcosDownloader(vmtype, v.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.ImagePath = dd.Get().LocalUncompressedFile
|
||||||
|
if err := dd.DownloadImage(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
v.ImagePath = dd.Get().LocalUncompressedFile
|
|
||||||
if err := dd.DownloadImage(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Add arch specific options including image location
|
// Add arch specific options including image location
|
||||||
v.CmdLine = append(v.CmdLine, v.addArchOptions()...)
|
v.CmdLine = append(v.CmdLine, v.addArchOptions()...)
|
||||||
|
|
||||||
|
@ -171,10 +184,20 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
originalDiskSize, err := getDiskSize(v.ImagePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Resize the disk image to input disk size
|
// Resize the disk image to input disk size
|
||||||
resize := exec.Command("qemu-img", []string{"resize", v.ImagePath, strconv.Itoa(int(opts.DiskSize)) + "G"}...)
|
// only if the virtualdisk size is less than
|
||||||
if err := resize.Run(); err != nil {
|
// the given disk size
|
||||||
return errors.Errorf("error resizing image: %q", err)
|
if opts.DiskSize<<(10*3) > originalDiskSize {
|
||||||
|
resize := exec.Command("qemu-img", []string{"resize", v.ImagePath, strconv.Itoa(int(opts.DiskSize)) + "G"}...)
|
||||||
|
resize.Stdout = os.Stdout
|
||||||
|
resize.Stderr = os.Stderr
|
||||||
|
if err := resize.Run(); err != nil {
|
||||||
|
return errors.Errorf("error resizing image: %q", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Write the ignition file
|
// Write the ignition file
|
||||||
ign := machine.DynamicIgnition{
|
ign := machine.DynamicIgnition{
|
||||||
|
@ -372,3 +395,34 @@ func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error {
|
||||||
|
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// executes qemu-image info to get the virtual disk size
|
||||||
|
// of the diskimage
|
||||||
|
func getDiskSize(path string) (uint64, error) {
|
||||||
|
diskInfo := exec.Command("qemu-img", "info", "--output", "json", path)
|
||||||
|
stdout, err := diskInfo.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if err := diskInfo.Start(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
tmpInfo := struct {
|
||||||
|
VirtualSize uint64 `json:"virtual-size"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
ClusterSize int64 `json:"cluster-size"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
FormatSpecific struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Data map[string]string `json:"data"`
|
||||||
|
}
|
||||||
|
DirtyFlag bool `json:"dirty-flag"`
|
||||||
|
}{}
|
||||||
|
if err := json.NewDecoder(stdout).Decode(&tmpInfo); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if err := diskInfo.Wait(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return tmpInfo.VirtualSize, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue