vorteil/vorteil

View on GitHub
pkg/cli/virtualizers.go

Summary

Maintainability
C
1 day
Test Coverage
F
0%
package cli

/**
 * SPDX-License-Identifier: Apache-2.0
 * Copyright 2020 vorteil.io Pty Ltd
 */

import (
    "context"
    "errors"
    "fmt"
    "io"
    "io/ioutil"
    "os"
    "path/filepath"
    "runtime"

    "github.com/beeker1121/goque"
    "github.com/thanhpk/randstr"
    "github.com/vorteil/vorteil/pkg/ext"
    "github.com/vorteil/vorteil/pkg/vcfg"
    "github.com/vorteil/vorteil/pkg/vdisk"
    "github.com/vorteil/vorteil/pkg/vimg"
    "github.com/vorteil/vorteil/pkg/virtualizers/firecracker"
    "github.com/vorteil/vorteil/pkg/virtualizers/hyperv"
    "github.com/vorteil/vorteil/pkg/virtualizers/iputil"
    "github.com/vorteil/vorteil/pkg/virtualizers/qemu"
    "github.com/vorteil/vorteil/pkg/virtualizers/virtualbox"
    "github.com/vorteil/vorteil/pkg/virtualizers/vmware"
    "github.com/vorteil/vorteil/pkg/vpkg"
)

var ips *goque.Queue

// saveDisk attempts to moves disk from sourceDisk to destDisk
func saveDisk(sourceDisk, destDisk string) error {
    p := log.NewProgress("Copying Disk to "+destDisk, "", 0)
    err := os.Rename(sourceDisk, destDisk)
    if err != nil {
        log.Errorf("Failed to Copy Disk to '%s' error: %v\f", destDisk, err)
        p.Finish(false)
        return err
    }

    p.Finish(true)
    log.Printf("Copied Disk")
    return nil
}

// buildFirecracker does the same thing as vdisk.Build but it returns me a calver of the kernel being used
func buildFirecracker(ctx context.Context, w io.WriteSeeker, cfg *vcfg.VCFG, args *vdisk.BuildArgs) (string, error) {
    var err error
    for i := range cfg.Networks {
        if ips == nil {
            ips, err = iputil.NewIPStack()
            if err != nil {
                return "", err
            }
            defer ips.Close()

        }
        ip, err := ips.Dequeue()
        if err != nil {
            return "", err
        }
        cfg.Networks[i].IP = ip.ToString()
        cfg.Networks[i].Gateway = iputil.BridgeIP
        cfg.Networks[i].Mask = "255.255.255.0"
    }
    vimgBuilder, err := vdisk.CreateBuilder(ctx, &vimg.BuilderArgs{
        Kernel: vimg.KernelOptions{
            Shell: args.KernelOptions.Shell,
        },
        FSCompiler: ext.NewCompiler(&ext.CompilerArgs{
            FileTree: args.PackageReader.FS(),
            Logger:   args.Logger,
        }),
        VCFG:   cfg,
        Logger: log,
    })
    if err != nil {
        return "", err
    }
    defer vimgBuilder.Close()
    vimgBuilder.SetDefaultMTU(args.Format.DefaultMTU())
    err = vdisk.NegotiateSize(ctx, vimgBuilder, cfg, args)
    if err != nil {
        return "", err
    }

    err = args.Format.Build(ctx, log, w, vimgBuilder, cfg)
    if err != nil {
        return "", err
    }
    return string(vimgBuilder.KernelUsed()), nil
}

// runVMware
//    Saves resulting image to diskOutput if it's not an empty string
func runVMware(pkgReader vpkg.Reader, cfg *vcfg.VCFG, name, diskOutput string) error {
    if !vmware.Allocator.IsAvailable() {
        return errors.New("vmware is not installed on your system")
    }

    var err error
    // Create base folder to store vmware vms so the socket can be grouped
    parent := fmt.Sprintf("%s-%s", vmware.VirtualizerID, randstr.Hex(5))
    parent = filepath.Join(os.TempDir(), parent)

    // Create parent directory as it doesn't exist
    err = os.MkdirAll(parent, os.ModePerm)
    if err != nil {
        return err
    }

    // need to create a tempfile rather than use the function to as vmware complains if the extension doesn't exist
    f, err := os.Create(filepath.Join(parent, "disk.vmdk"))
    if err != nil {
        return err
    }

    defer func() {
        f.Close()
        // Move disk to diskOutput
        if diskOutput != "" {
            saveDisk(f.Name(), diskOutput)
        }
        os.Remove(f.Name())
        os.Remove(parent)

    }()

    err = vdisk.Build(context.Background(), f, &vdisk.BuildArgs{
        WithVCFGDefaults: true,
        PackageReader:    pkgReader,
        Format:           vmware.Allocator.DiskFormat(),
        KernelOptions: vdisk.KernelOptions{
            Shell:  flagShell,
            Record: flagRecord != "",
        },
        Logger: log,
    })
    if err != nil {
        return err
    }

    err = f.Close()
    if err != nil {
        return err
    }

    err = pkgReader.Close()
    if err != nil {
        return err
    }

    alloc := vmware.Allocator
    virt := alloc.Alloc()

    config := vmware.Config{
        Headless:    !flagGUI,
        NetworkType: "nat",
    }

    err = virt.Initialize(config.Marshal())
    if err != nil {
        return err
    }

    err = vcfg.WithDefaults(cfg, log)
    if err != nil {
        return err
    }

    return run(virt, f.Name(), cfg, name)
}

// runFirecracker needs a longer build process so we can pull the calver of the kernel used to build the disk
//    Saves resulting image to diskOutput if it's not an empty string
func runFirecracker(pkgReader vpkg.Reader, cfg *vcfg.VCFG, name, diskOutput string) error {
    var err error
    if runtime.GOOS != "linux" {
        return errors.New("firecracker is only available on linux")
    }
    if !firecracker.Allocator.IsAvailable() {
        return errors.New("firecracker is not installed on your system")
    }

    err = firecracker.FetchBridgeDevice()
    if err != nil {
        // Set bridge device to 10.26.10.1
        err = firecracker.SetupBridge(log, iputil.BridgeIP)
        if err != nil {
            return err
        }
    }

    // Create base folder to store firecracker vms so the socket can be grouped
    parent := fmt.Sprintf("%s-%s", firecracker.VirtualizerID, randstr.Hex(5))
    parent = filepath.Join(os.TempDir(), parent)
    defer os.RemoveAll(parent)

    // Create parent directory as it doesn't exist
    err = os.MkdirAll(parent, os.ModePerm)
    if err != nil {
        return err
    }

    f, err := ioutil.TempFile(parent, "vorteil.disk")
    if err != nil {
        return err
    }

    defer func() {
        f.Close()
        // Move disk to diskOutput
        if diskOutput != "" {
            saveDisk(f.Name(), diskOutput)
        }
        os.Remove(f.Name())
        os.Remove(parent)

    }()

    err = vcfg.WithDefaults(cfg, log)
    if err != nil {
        return err
    }

    kernelVer, err := buildFirecracker(context.Background(), f, cfg, &vdisk.BuildArgs{
        WithVCFGDefaults: true,
        PackageReader:    pkgReader,
        Format:           firecracker.Allocator.DiskFormat(),
        KernelOptions: vdisk.KernelOptions{
            Shell:  flagShell,
            Record: flagRecord != "",
        },
        Logger: log,
    })
    if err != nil {
        return err
    }

    // assign kernel version that was built with vcfg
    cfg.VM.Kernel = kernelVer

    err = f.Close()
    if err != nil {
        return err
    }

    err = pkgReader.Close()
    if err != nil {
        return err
    }

    alloc := firecracker.Allocator
    virt := alloc.Alloc()

    if flagGUI {
        log.Warnf("firecracker does not support displaying a gui")
    }

    config := firecracker.Config{}

    err = virt.Initialize(config.Marshal())
    if err != nil {
        return err
    }

    return run(virt, f.Name(), cfg, name)
}

// runHyperV
//    Saves resulting image to diskOutput if it's not an empty string
func runHyperV(pkgReader vpkg.Reader, cfg *vcfg.VCFG, name, diskOutput string) error {
    if runtime.GOOS != "windows" {
        return errors.New("hyper-v is only available on windows system")
    }
    if !hyperv.Allocator.IsAvailable() {
        return errors.New("hyper-v is not enabled on your system")
    }
    // Create base folder to store hyper-v vms so the socket can be grouped
    parent := fmt.Sprintf("%s-%s", hyperv.VirtualizerID, randstr.Hex(5))
    parent = filepath.Join(os.TempDir(), parent)

    // Create parent directory as it doesn't exist
    err := os.MkdirAll(parent, os.ModePerm)
    if err != nil {
        return err
    }

    // need to create a tempfile rather than use the function to as hyper-v complains if the extension doesn't exist
    f, err := os.Create(filepath.Join(parent, "disk.vhd"))
    if err != nil {
        return err
    }

    defer func() {
        f.Close()
        // Move disk to diskOutput
        if diskOutput != "" {
            saveDisk(f.Name(), diskOutput)
        }
        os.Remove(f.Name())
        os.Remove(parent)

    }()

    err = vdisk.Build(context.Background(), f, &vdisk.BuildArgs{
        WithVCFGDefaults: true,
        PackageReader:    pkgReader,
        Format:           hyperv.Allocator.DiskFormat(),
        KernelOptions: vdisk.KernelOptions{
            Shell:  flagShell,
            Record: flagRecord != "",
        },
        Logger: log,
    })
    if err != nil {
        return err
    }

    err = f.Close()
    if err != nil {
        return err
    }

    err = pkgReader.Close()
    if err != nil {
        return err
    }

    alloc := hyperv.Allocator
    virt := alloc.Alloc()

    config := hyperv.Config{
        Headless:   !flagGUI,
        SwitchName: "Default Switch",
    }

    err = virt.Initialize(config.Marshal())
    if err != nil {
        return err
    }

    err = vcfg.WithDefaults(cfg, log)
    if err != nil {
        return err
    }

    return run(virt, f.Name(), cfg, name)
}

// runVirtualBox
//    Saves resulting image to diskOutput if it's not an empty string
func runVirtualBox(pkgReader vpkg.Reader, cfg *vcfg.VCFG, name, diskOutput string) error {
    if !virtualbox.Allocator.IsAvailable() {
        return errors.New("virtualbox not found installed on system")
    }
    // Create base folder to store virtualbox vms so the socket can be grouped
    parent := fmt.Sprintf("%s-%s", virtualbox.VirtualizerID, randstr.Hex(5))
    parent = filepath.Join(os.TempDir(), parent)
    defer os.RemoveAll(parent)

    // Create parent directory as it doesn't exist
    err := os.MkdirAll(parent, os.ModePerm)
    if err != nil {
        return err
    }

    f, err := ioutil.TempFile(parent, "vorteil.disk")
    if err != nil {
        return err
    }

    defer func() {
        f.Close()
        // Move disk to diskOutput
        if diskOutput != "" {
            saveDisk(f.Name(), diskOutput)
        }
        os.Remove(f.Name())
        os.Remove(parent)

    }()

    err = vdisk.Build(context.Background(), f, &vdisk.BuildArgs{
        WithVCFGDefaults: true,
        PackageReader:    pkgReader,
        Format:           virtualbox.Allocator.DiskFormat(),
        KernelOptions: vdisk.KernelOptions{
            Shell:  flagShell,
            Record: flagRecord != "",
        },
        Logger: log,
    })
    if err != nil {
        return err
    }

    err = f.Close()
    if err != nil {
        return err
    }

    err = pkgReader.Close()
    if err != nil {
        return err
    }

    alloc := virtualbox.Allocator
    virt := alloc.Alloc()

    config := virtualbox.Config{
        Headless:    !flagGUI,
        NetworkType: "nat",
    }

    err = virt.Initialize(config.Marshal())
    if err != nil {
        return err
    }

    err = vcfg.WithDefaults(cfg, log)
    if err != nil {
        return err
    }

    return run(virt, f.Name(), cfg, name)
}

// runQEMU
//    Saves resulting image to diskOutput if it's not an empty string
func runQEMU(pkgReader vpkg.Reader, cfg *vcfg.VCFG, name string, diskOutput string) error {

    if !qemu.Allocator.IsAvailable() {
        return errors.New("qemu not installed on system")
    }
    // Create base folder to store qemu vms so the socket can be grouped
    parent := fmt.Sprintf("%s-%s", qemu.VirtualizerID, randstr.Hex(5))
    parent = filepath.Join(os.TempDir(), parent)
    defer os.RemoveAll(parent)
    // Create parent directory as it doesn't exist
    err := os.MkdirAll(parent, os.ModePerm)
    if err != nil {
        return err
    }
    f, err := ioutil.TempFile(parent, "vorteil.disk")
    if err != nil {
        return err
    }

    defer func() {
        f.Close()
        // Move disk to diskOutput
        if diskOutput != "" {
            saveDisk(f.Name(), diskOutput)
        }
        os.Remove(f.Name())
        os.Remove(parent)

    }()

    err = vdisk.Build(context.Background(), f, &vdisk.BuildArgs{
        WithVCFGDefaults: true,
        PackageReader:    pkgReader,
        Format:           qemu.Allocator.DiskFormat(),
        KernelOptions: vdisk.KernelOptions{
            Shell:  flagShell,
            Record: flagRecord != "",
        },
        Logger: log,
    })
    if err != nil {
        return err
    }

    err = f.Close()
    if err != nil {
        return err
    }

    err = pkgReader.Close()
    if err != nil {
        return err
    }

    alloc := qemu.Allocator
    virt := alloc.Alloc()

    config := qemu.Config{
        Headless: !flagGUI,
    }

    err = virt.Initialize(config.Marshal())
    if err != nil {
        return err
    }

    err = vcfg.WithDefaults(cfg, log)
    if err != nil {
        return err
    }

    return run(virt, f.Name(), cfg, name)
}