mirror of
https://github.com/containers/podman.git
synced 2025-05-21 00:56:36 +08:00
machine refactoring preparations for hyperv
before we can support hyperv as a virtualization option for podman machine, several areas in machine will require cleanup. this is the first pass of these changes to keep the review burden low. changes include: * convert artifact, format (image format) and compression to enums with string methods * rename Provider interface to VirtProvider * change Provider implementation in QEMU to QEMUVirt * change Provider implementation in WSL to WSLVirt as mentioned earlier, there will be several more of these refactoring PRs because assumptions were made about associations of platforms and virt providers as well as compression and image formats. Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
@ -8,6 +8,6 @@ import (
|
||||
"github.com/containers/podman/v4/pkg/machine/qemu"
|
||||
)
|
||||
|
||||
func GetSystemDefaultProvider() machine.Provider {
|
||||
func GetSystemDefaultProvider() machine.VirtProvider {
|
||||
return qemu.GetVirtualizationProvider()
|
||||
}
|
||||
|
@ -5,6 +5,6 @@ import (
|
||||
"github.com/containers/podman/v4/pkg/machine/wsl"
|
||||
)
|
||||
|
||||
func GetSystemDefaultProvider() machine.Provider {
|
||||
func GetSystemDefaultProvider() machine.VirtProvider {
|
||||
return wsl.GetWSLProvider()
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ var (
|
||||
vmtype = "apple"
|
||||
)
|
||||
|
||||
func GetVirtualizationProvider() machine.Provider {
|
||||
func GetVirtualizationProvider() machine.VirtProvider {
|
||||
return hvProvider
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ const (
|
||||
DefaultMachineName string = "podman-machine-default"
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
type VirtProvider interface {
|
||||
NewMachine(opts InitOptions) (VM, error)
|
||||
LoadVMByName(name string) (VM, error)
|
||||
List(opts ListOptions) ([]*ListResponse, error)
|
||||
@ -72,10 +72,10 @@ var (
|
||||
|
||||
type Download struct {
|
||||
Arch string
|
||||
Artifact string
|
||||
Artifact artifact
|
||||
CompressionType string
|
||||
CacheDir string
|
||||
Format string
|
||||
Format imageFormat
|
||||
ImageName string
|
||||
LocalPath string
|
||||
LocalUncompressedFile string
|
||||
|
@ -43,7 +43,7 @@ func TestMachine(t *testing.T) {
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
fcd, err := machine.GetFCOSDownload(defaultStream)
|
||||
fcd, err := machine.GetFCOSDownload(defaultStream, machine.Xz)
|
||||
if err != nil {
|
||||
Fail("unable to get virtual machine image")
|
||||
}
|
||||
|
@ -18,17 +18,13 @@ import (
|
||||
"github.com/coreos/stream-metadata-go/fedoracoreos"
|
||||
"github.com/coreos/stream-metadata-go/release"
|
||||
"github.com/coreos/stream-metadata-go/stream"
|
||||
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// These should eventually be moved into machine/qemu as
|
||||
// they are specific to running qemu
|
||||
var (
|
||||
artifact = "qemu"
|
||||
Format = "qcow2.xz"
|
||||
)
|
||||
type imageCompression int64
|
||||
type artifact int64
|
||||
type imageFormat int64
|
||||
|
||||
const (
|
||||
// Used for testing the latest podman in fcos
|
||||
@ -36,14 +32,76 @@ const (
|
||||
podmanTesting = "podman-testing"
|
||||
PodmanTestingHost = "fedorapeople.org"
|
||||
PodmanTestingURL = "groups/podman/testing"
|
||||
|
||||
Xz imageCompression = iota
|
||||
Zip
|
||||
Gz
|
||||
Bz2
|
||||
|
||||
Qemu artifact = iota
|
||||
HyperV
|
||||
None
|
||||
|
||||
qcow imageFormat = iota
|
||||
vhdx
|
||||
Tar
|
||||
)
|
||||
|
||||
//
|
||||
// TODO artifact, imageformat, and imagecompression should be probably combined into some sort
|
||||
// of object which can "produce" the correct output we are looking for bc things like
|
||||
// image format contain both the image type AND the compression. This work can be done before
|
||||
// or after the hyperv work. For now, my preference is to NOT change things and just get things
|
||||
// typed strongly
|
||||
//
|
||||
|
||||
func (a artifact) String() string {
|
||||
if a == HyperV {
|
||||
return "hyperv"
|
||||
}
|
||||
return "qemu"
|
||||
}
|
||||
|
||||
func (imf imageFormat) String() string {
|
||||
switch imf {
|
||||
case vhdx:
|
||||
return "vhdx.zip"
|
||||
case Tar:
|
||||
return "tar.xz"
|
||||
}
|
||||
return "qcow2.xz"
|
||||
}
|
||||
|
||||
func (c imageCompression) String() string {
|
||||
switch c {
|
||||
case Gz:
|
||||
return "gz"
|
||||
case Zip:
|
||||
return "zip"
|
||||
case Bz2:
|
||||
return "bz2"
|
||||
}
|
||||
return "xz"
|
||||
}
|
||||
|
||||
func compressionFromFile(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
|
||||
}
|
||||
|
||||
type FcosDownload struct {
|
||||
Download
|
||||
}
|
||||
|
||||
func NewFcosDownloader(vmType, vmName, imageStream string) (DistributionDownload, error) {
|
||||
info, err := GetFCOSDownload(imageStream)
|
||||
info, err := GetFCOSDownload(imageStream, Xz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -62,9 +120,9 @@ func NewFcosDownloader(vmType, vmName, imageStream string) (DistributionDownload
|
||||
fcd := FcosDownload{
|
||||
Download: Download{
|
||||
Arch: GetFcosArch(),
|
||||
Artifact: artifact,
|
||||
Artifact: Qemu,
|
||||
CacheDir: cacheDir,
|
||||
Format: Format,
|
||||
Format: qcow,
|
||||
ImageName: imageName,
|
||||
LocalPath: filepath.Join(cacheDir, imageName),
|
||||
Sha256sum: info.Sha256Sum,
|
||||
@ -151,7 +209,7 @@ func getStreamURL(streamType string) url2.URL {
|
||||
|
||||
// This should get Exported and stay put as it will apply to all fcos downloads
|
||||
// getFCOS parses fedoraCoreOS's stream and returns the image download URL and the release version
|
||||
func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) {
|
||||
func GetFCOSDownload(imageStream string, compression imageCompression) (*FcosDownloadInfo, error) {
|
||||
var (
|
||||
fcosstable stream.Stream
|
||||
altMeta release.Release
|
||||
@ -193,7 +251,7 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) {
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to pull VM image: no targetArch in stream")
|
||||
}
|
||||
qcow2, ok := arches.Media.Qemu.Artifacts["qcow2.xz"]
|
||||
qcow2, ok := arches.Media.Qemu.Artifacts[qcow.String()]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to pull VM image: no qcow2.xz format in stream")
|
||||
}
|
||||
@ -202,7 +260,7 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) {
|
||||
return &FcosDownloadInfo{
|
||||
Location: disk.Location,
|
||||
Sha256Sum: disk.Sha256,
|
||||
CompressionType: "xz",
|
||||
CompressionType: compression.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -217,7 +275,7 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) {
|
||||
if artifacts == nil {
|
||||
return nil, fmt.Errorf("unable to pull VM image: no artifact in stream")
|
||||
}
|
||||
qemu, ok := artifacts[artifact]
|
||||
qemu, ok := artifacts[Qemu.String()]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to pull VM image: no qemu artifact in stream")
|
||||
}
|
||||
@ -225,7 +283,7 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) {
|
||||
if formats == nil {
|
||||
return nil, fmt.Errorf("unable to pull VM image: no formats in stream")
|
||||
}
|
||||
qcow, ok := formats[Format]
|
||||
qcow, ok := formats[qcow.String()]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to pull VM image: no qcow2.xz format in stream")
|
||||
}
|
||||
@ -237,6 +295,6 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) {
|
||||
Location: disk.Location,
|
||||
Release: qemu.Release,
|
||||
Sha256Sum: disk.Sha256,
|
||||
CompressionType: "xz",
|
||||
CompressionType: compression.String(),
|
||||
}, nil
|
||||
}
|
||||
|
143
pkg/machine/fcos_test.go
Normal file
143
pkg/machine/fcos_test.go
Normal file
@ -0,0 +1,143 @@
|
||||
package machine
|
||||
|
||||
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 := compressionFromFile(tt.args.path); got != tt.want {
|
||||
t.Errorf("compressionFromFile() = %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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageFormat_String(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
imf imageFormat
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "vhdx.zip",
|
||||
imf: vhdx,
|
||||
want: "vhdx.zip",
|
||||
},
|
||||
{
|
||||
name: "qcow2",
|
||||
imf: qcow,
|
||||
want: "qcow2.xz",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.imf.String(); got != tt.want {
|
||||
t.Errorf("String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_artifact_String(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a artifact
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "qemu",
|
||||
a: Qemu,
|
||||
want: "qemu",
|
||||
},
|
||||
{
|
||||
name: "hyperv",
|
||||
a: HyperV,
|
||||
want: "hyperv",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.a.String(); got != tt.want {
|
||||
t.Errorf("String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -86,19 +86,9 @@ func supportedURL(path string) (url *url2.URL) {
|
||||
}
|
||||
|
||||
func (d Download) GetLocalUncompressedFile(dataDir string) string {
|
||||
var (
|
||||
extension string
|
||||
)
|
||||
switch {
|
||||
case strings.HasSuffix(d.LocalPath, ".bz2"):
|
||||
extension = ".bz2"
|
||||
case strings.HasSuffix(d.LocalPath, ".gz"):
|
||||
extension = ".gz"
|
||||
case strings.HasSuffix(d.LocalPath, ".xz"):
|
||||
extension = ".xz"
|
||||
}
|
||||
extension := compressionFromFile(dataDir)
|
||||
uncompressedFilename := d.VMName + "_" + d.ImageName
|
||||
return filepath.Join(dataDir, strings.TrimSuffix(uncompressedFilename, extension))
|
||||
return filepath.Join(dataDir, strings.TrimSuffix(uncompressedFilename, extension.String()))
|
||||
}
|
||||
|
||||
func (g GenericDownload) Get() *Download {
|
||||
@ -224,11 +214,11 @@ func Decompress(localPath, uncompressedPath string) error {
|
||||
|
||||
// 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
|
||||
// depends on Xz: not pre-installed on mac, so it becomes a brew dependency
|
||||
func decompressXZ(src string, output io.WriteCloser) error {
|
||||
var read io.Reader
|
||||
var cmd *exec.Cmd
|
||||
// Prefer xz utils for fastest performance, fallback to go xi2 impl
|
||||
// Prefer Xz utils for fastest performance, fallback to go xi2 impl
|
||||
if _, err := exec.LookPath("xz"); err == nil {
|
||||
cmd = exec.Command("xz", "-d", "-c", "-k", src)
|
||||
read, err = cmd.StdoutPipe()
|
||||
|
@ -19,7 +19,7 @@ const (
|
||||
|
||||
)
|
||||
|
||||
type Provider struct{}
|
||||
type Virtualization struct{}
|
||||
|
||||
// Deprecated: MachineVMV1 is being deprecated in favor a more flexible and informative
|
||||
// structure
|
||||
|
@ -35,12 +35,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
qemuProvider = &Provider{}
|
||||
qemuProvider = &Virtualization{}
|
||||
// vmtype refers to qemu (vs libvirt, krun, etc).
|
||||
vmtype = "qemu"
|
||||
)
|
||||
|
||||
func GetVirtualizationProvider() machine.Provider {
|
||||
func GetVirtualizationProvider() machine.VirtProvider {
|
||||
return qemuProvider
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ const (
|
||||
|
||||
// NewMachine initializes an instance of a virtual machine based on the qemu
|
||||
// virtualization.
|
||||
func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
|
||||
func (p *Virtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
|
||||
vmConfigDir, err := machine.GetConfDir(vmtype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -231,7 +231,7 @@ func migrateVM(configPath string, config []byte, vm *MachineVM) error {
|
||||
|
||||
// LoadVMByName reads a json file that describes a known qemu vm
|
||||
// and returns a vm instance
|
||||
func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
|
||||
func (p *Virtualization) LoadVMByName(name string) (machine.VM, error) {
|
||||
vm := &MachineVM{Name: name}
|
||||
vm.HostUser = machine.HostUser{UID: -1} // posix reserves -1, so use it to signify undefined
|
||||
if err := vm.update(); err != nil {
|
||||
@ -1117,7 +1117,7 @@ func getDiskSize(path string) (uint64, error) {
|
||||
}
|
||||
|
||||
// List lists all vm's that use qemu virtualization
|
||||
func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
|
||||
func (p *Virtualization) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
|
||||
return getVMInfos()
|
||||
}
|
||||
|
||||
@ -1193,7 +1193,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
|
||||
return listed, err
|
||||
}
|
||||
|
||||
func (p *Provider) IsValidVMName(name string) (bool, error) {
|
||||
func (p *Virtualization) IsValidVMName(name string) (bool, error) {
|
||||
infos, err := getVMInfos()
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -1208,7 +1208,7 @@ func (p *Provider) IsValidVMName(name string) (bool, error) {
|
||||
|
||||
// CheckExclusiveActiveVM checks if there is a VM already running
|
||||
// that does not allow other VMs to be running
|
||||
func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
|
||||
func (p *Virtualization) CheckExclusiveActiveVM() (bool, string, error) {
|
||||
vms, err := getVMInfos()
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("checking VM active: %w", err)
|
||||
@ -1670,7 +1670,7 @@ func (v *MachineVM) editCmdLine(flag string, value string) {
|
||||
}
|
||||
|
||||
// RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine
|
||||
func (p *Provider) RemoveAndCleanMachines() error {
|
||||
func (p *Virtualization) RemoveAndCleanMachines() error {
|
||||
var (
|
||||
vm machine.VM
|
||||
listResponse []*machine.ListResponse
|
||||
@ -1745,7 +1745,7 @@ func (p *Provider) RemoveAndCleanMachines() error {
|
||||
return prevErr
|
||||
}
|
||||
|
||||
func (p *Provider) VMType() string {
|
||||
func (p *Virtualization) VMType() string {
|
||||
return vmtype
|
||||
}
|
||||
|
||||
|
@ -43,9 +43,9 @@ func NewFedoraDownloader(vmType, vmName, releaseStream string) (machine.Distribu
|
||||
f := FedoraDownload{
|
||||
Download: machine.Download{
|
||||
Arch: machine.GetFcosArch(),
|
||||
Artifact: "",
|
||||
Artifact: machine.None,
|
||||
CacheDir: cacheDir,
|
||||
Format: machine.Format,
|
||||
Format: machine.Tar,
|
||||
ImageName: imageName,
|
||||
LocalPath: filepath.Join(cacheDir, imageName),
|
||||
URL: downloadURL,
|
||||
|
@ -28,7 +28,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
wslProvider = &Provider{}
|
||||
wslProvider = &Virtualization{}
|
||||
// vmtype refers to qemu (vs libvirt, krun, etc)
|
||||
vmtype = "wsl"
|
||||
)
|
||||
@ -204,7 +204,7 @@ const (
|
||||
globalPipe = "docker_engine"
|
||||
)
|
||||
|
||||
type Provider struct{}
|
||||
type Virtualization struct{}
|
||||
|
||||
type MachineVM struct {
|
||||
// ConfigPath is the path to the configuration file
|
||||
@ -235,12 +235,12 @@ func (e *ExitCodeError) Error() string {
|
||||
return fmt.Sprintf("Process failed with exit code: %d", e.code)
|
||||
}
|
||||
|
||||
func GetWSLProvider() machine.Provider {
|
||||
func GetWSLProvider() machine.VirtProvider {
|
||||
return wslProvider
|
||||
}
|
||||
|
||||
// NewMachine initializes an instance of a wsl machine
|
||||
func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
|
||||
func (p *Virtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
|
||||
vm := new(MachineVM)
|
||||
if len(opts.Name) > 0 {
|
||||
vm.Name = opts.Name
|
||||
@ -281,7 +281,7 @@ func getConfigPathExt(name string, extension string) (string, error) {
|
||||
|
||||
// LoadByName reads a json file that describes a known qemu vm
|
||||
// and returns a vm instance
|
||||
func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
|
||||
func (p *Virtualization) LoadVMByName(name string) (machine.VM, error) {
|
||||
configPath, err := getConfigPath(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -1418,7 +1418,7 @@ func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error {
|
||||
}
|
||||
|
||||
// List lists all vm's that use qemu virtualization
|
||||
func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
|
||||
func (p *Virtualization) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
|
||||
return GetVMInfos()
|
||||
}
|
||||
|
||||
@ -1547,7 +1547,7 @@ func getMem(vm *MachineVM) (uint64, error) {
|
||||
return total - available, err
|
||||
}
|
||||
|
||||
func (p *Provider) IsValidVMName(name string) (bool, error) {
|
||||
func (p *Virtualization) IsValidVMName(name string) (bool, error) {
|
||||
infos, err := GetVMInfos()
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -1560,7 +1560,7 @@ func (p *Provider) IsValidVMName(name string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
|
||||
func (p *Virtualization) CheckExclusiveActiveVM() (bool, string, error) {
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
@ -1619,7 +1619,7 @@ func (v *MachineVM) getResources() (resources machine.ResourceConfig) {
|
||||
}
|
||||
|
||||
// RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine
|
||||
func (p *Provider) RemoveAndCleanMachines() error {
|
||||
func (p *Virtualization) RemoveAndCleanMachines() error {
|
||||
var (
|
||||
vm machine.VM
|
||||
listResponse []*machine.ListResponse
|
||||
@ -1694,6 +1694,6 @@ func (p *Provider) RemoveAndCleanMachines() error {
|
||||
return prevErr
|
||||
}
|
||||
|
||||
func (p *Provider) VMType() string {
|
||||
func (p *Virtualization) VMType() string {
|
||||
return vmtype
|
||||
}
|
||||
|
Reference in New Issue
Block a user