vorteil/vorteil

View on GitHub
pkg/virtualizers/util.go

Summary

Maintainability
D
1 day
Test Coverage
package virtualizers

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

import (
    "bytes"
    "fmt"
    "net"
    "os"
    "os/exec"
    "path/filepath"
    "runtime"
    "strings"
)

// HostDevices is a virtualbox only function which returns a list of available host devices
func HostDevices() ([]string, error) {
    var hostDevices []string
    virtualizers, err := Backends()
    if err != nil {
        return nil, err
    }

    vboxInstalled := false
    for _, v := range virtualizers {
        if v == "virtualbox" {
            vboxInstalled = true
        }
    }

    if !vboxInstalled {
        return nil, fmt.Errorf("virtualbox must be installed to run this function")
    }

    cmd := exec.Command("VBoxManage", "list", "hostonlyifs")
    b, err := cmd.CombinedOutput()
    if err != nil {
        return nil, err
    }

    lines := strings.Split(fmt.Sprintf("%s", b), "\n")
    for _, line := range lines {
        if strings.Contains(line, "Name:") {
            if strings.TrimSpace(strings.Split(strings.TrimPrefix(line, "Name:"), "VBoxNetworkName:")[0]) != "" {
                hostDevices = append(hostDevices, strings.TrimSpace(strings.Split(strings.TrimPrefix(line, "Name:"), "VBoxNetworkName:")[0]))

            }
        }
    }
    return hostDevices, nil
}

// VSwitches is a windows only function which returns the virtual switches hyper-v responds with
func VSwitches() ([]string, error) {
    virtualizers, err := Backends()
    if err != nil {
        return nil, err
    }

    hyperVInstalled := false
    for _, v := range virtualizers {
        if v == "hyperv" {
            hyperVInstalled = true
        }
    }
    if !hyperVInstalled {
        return nil, fmt.Errorf("hyperv must be available to run this function")
    }

    cmd := exec.Command(Powershell, "Get-VMSwitch", "|", "Select", "Name")
    b, err := cmd.CombinedOutput()
    if err != nil {
        if !strings.Contains(err.Error(), "exit status 1") {
            return nil, err
        } else {
            return nil, fmt.Errorf("%s", string(b))
        }
    }

    lines := strings.Split(fmt.Sprintf("%s", b), "----")
    if len(lines) >= 2 {
        split := strings.Split(strings.TrimSpace(lines[1]), "\n")
        return split, nil
    }

    return nil, fmt.Errorf("no virtual switches are created in the hyperv manager")

}

// BridgedDevices is a virtualbox only function which returns an array of available bridged devices
func BridgedDevices() ([]string, error) {
    var bridgedDevices []string
    virtualizers, err := Backends()
    if err != nil {
        return nil, err
    }
    vboxInstalled := false
    for _, v := range virtualizers {
        if v == "virtualbox" {
            vboxInstalled = true
        }
    }
    if !vboxInstalled {
        return nil, fmt.Errorf("virtualbox must be installed to run this function")
    }
    cmd := exec.Command("VBoxManage", "list", "bridgedifs")
    b, err := cmd.CombinedOutput()
    if err != nil {
        return nil, err
    }

    check := make(map[string]int)
    lines := strings.Split(fmt.Sprintf("%s", b), "\n")
    for _, line := range lines {
        if strings.HasPrefix(line, "Name:") {
            device := strings.TrimSpace(strings.Split(line, "Name:")[1])
            check[device] = 1
        }
    }
    for device := range check {
        bridgedDevices = append(bridgedDevices, device)
    }

    return bridgedDevices, nil
}

// CheckNameExistsVirtualBox checks the virtualbox list to see if a vm with the same name
// has already been created
func CheckNameExistsVirtualBox(name string) (bool, error) {

    command := exec.Command("VBoxManage", "showvminfo", name)
    var outB, errB bytes.Buffer
    command.Stdout = &outB
    command.Stderr = &errB

    err := command.Run()
    if err != nil {
        errMsg := strings.Split(fmt.Sprintf("%s", command.Stderr), "\n")[0]
        if errMsg == fmt.Sprintf("VBoxManage: error: Could not find a registered machine named '%s'", name) {
            return false, nil
        }
        return false, err
    }

    return true, nil
}

// CheckNameExistsHyperV checks the hyperv driver to see if a vm with the same name already exists
func CheckNameExistsHyperV(name string) (bool, error) {
    command := exec.Command(Powershell, "Get-VM", "|", "Select", "Name")
    var outB, errB bytes.Buffer
    command.Stdout = &outB
    command.Stderr = &errB
    err := command.Run()
    if err != nil {
        return false, err
    }
    list := strings.Split(strings.TrimSpace(outB.String()), "----")
    if len(list) > 1 {
        vmlist := strings.Split(strings.TrimSpace(list[1]), "\n")
        for _, vm := range vmlist {
            if vm == name {
                return true, nil
            }
        }
    }

    return false, nil
}

// BindPort attempts to bind ports and if not available will assign a different port.
func BindPort(netType, protocol, port string) (string, string, error) {
    var (
        bind     string
        netRoute string
        isBound  bool
    )

    if netType == "nat" {
        netRoute = fmt.Sprintf("localhost:%s", port)
        // log attempting to bind
        switch protocol {
        case "udp":
            addr, err := net.ResolveUDPAddr("udp4", netRoute)
            if err != nil {
                return "", netRoute, err
            }
            listener, err := net.ListenUDP("udp4", addr)
            if err == nil {
                s := strings.Split(listener.LocalAddr().String(), ":")
                bind = s[len(s)-1]
                isBound = true
                listener.Close()
            }
        default:
            listener, err := net.Listen("tcp4", fmt.Sprintf(":%s", port))
            if err == nil {
                s := strings.Split(listener.Addr().String(), ":")
                bind = s[len(s)-1]
                isBound = true
                listener.Close()
            }
        }
        if !isBound {
            // log that it failed to bind netRoute
            netRoute = "localhost:0"
            switch protocol {
            case "udp":
                addr, err := net.ResolveUDPAddr("udp4", netRoute)
                if err != nil {
                    return "", netRoute, err
                }
                listener, err := net.ListenUDP("udp4", addr)
                if err == nil {
                    s := strings.Split(listener.LocalAddr().String(), ":")
                    bind = s[len(s)-1]
                    isBound = true
                    netRoute = "localhost:" + bind
                    listener.Close()
                } else {
                    return "", netRoute, err
                }
            default:
                listener, err := net.Listen("tcp4", netRoute)
                if err == nil {
                    s := strings.Split(listener.Addr().String(), ":")
                    bind = s[len(s)-1]
                    isBound = true
                    netRoute = "localhost:" + bind
                    listener.Close()
                } else {
                    return "", netRoute, err
                }
            }
        }
    }
    // Bound on address netRoute
    return bind, netRoute, nil
}

// GetExecutable returns the name of the executable for the virtualizer.
func GetExecutable(virtualizer string) (string, error) {
    switch virtualizer {
    case "qemu":
        return "qemu-system-x86_64", nil
    case "virtualbox":
        return "VBoxManage", nil
    case "vmware":
        return "vmrun", nil
    case "firecracker":
        return "firecracker", nil
    case "hyperv":
        return Powershell, nil
    default:
        return "", fmt.Errorf("%s is not supported", virtualizer)
    }
}

// Backends returns the currently available hypervisors the system running on.
func Backends() ([]string, error) {
    var installedVirtualizers []string
    path := os.Getenv("PATH")
    separated := ":"
    if runtime.GOOS == "windows" {
        separated = ";"
    }
    if !strings.Contains(path, vbox) {
        err := os.Setenv("PATH", fmt.Sprintf("%s%s%s", path, separated, vbox))
        if err != nil {
            return nil, err
        }
    }
    path = os.Getenv("PATH")

    // VMware 16 changed where vmrun is located on windows
    if runtime.GOOS == "windows" {
        if !strings.Contains(path, fmt.Sprintf("%s;", vmware)) {
            err := os.Setenv("PATH", fmt.Sprintf("%s%s%s", path, separated, vmware))
            if err != nil {
                return nil, err
            }
        }
    }

    path = os.Getenv("PATH")

    if !strings.Contains(path, vmware) {
        err := os.Setenv("PATH", fmt.Sprintf("%s%s%s", path, separated, vmware))
        if err != nil {
            return nil, err
        }
    }
    path = os.Getenv("PATH")

    if !strings.Contains(path, qemu) {
        err := os.Setenv("PATH", fmt.Sprintf("%s%s%s", path, separated, qemu))
        if err != nil {
            return nil, err
        }
    }

    path = os.Getenv("PATH")

    if runtime.GOOS == "linux" {
        if !strings.Contains(path, firecracker) {
            err := os.Setenv("PATH", fmt.Sprintf("%s%s%s", path, separated, firecracker))
            if err != nil {
                return nil, err
            }
        }
        path = os.Getenv("PATH")
    }

    paths := filepath.SplitList(path)

    for _, v := range supportedVirtualizers {
        if v == "hyperv" && runtime.GOOS != "windows" {
            continue
        } else {
            virt, err := GetExecutable(v)
            if err != nil {
                break
            }
            if runtime.GOOS == "windows" {
                virt = virt + ".exe"
            }
            for _, p := range paths {
                p := filepath.Join(p, virt)
                _, err = os.Stat(p)
                if err == nil {
                    found := false
                    for _, virts := range installedVirtualizers {
                        if virts == v {
                            found = true
                        }
                    }
                    if !found {
                        installedVirtualizers = append(installedVirtualizers, v)
                    }
                }
            }
        }

    }

    // If we're on windows check to see if hyperv by checking if the ethernet adapter is online
    if runtime.GOOS == "windows" {
        cmd := exec.Command("ipconfig", "/all")
        // cmd := exec.Command(Powershell, "Get-WindowsOptionalFeature", "-FeatureName", "Microsoft-Hyper-V-All", "-Online")
        resp, err := cmd.CombinedOutput()
        if err != nil {
            fmt.Printf("error checking for hyperv: %v\n", err)
        }
        output := string(resp)

        if strings.Contains(output, "Hyper-V Virtual Ethernet Adapter") {
            installedVirtualizers = append(installedVirtualizers, "hyperv")
        }
    }
    return installedVirtualizers, nil
}