Files
podman/vendor/github.com/crc-org/vfkit/pkg/config/virtio.go
renovate[bot] 6a96f70180 fix(deps): update module github.com/crc-org/vfkit to v0.6.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-12 15:14:39 +00:00

883 lines
24 KiB
Go

package config
import (
"fmt"
"math"
"net"
"os"
"strconv"
"strings"
"time"
)
// The VirtioDevice interface is an interface which is implemented by all virtio devices.
type VirtioDevice VMComponent
const (
// Possible values for VirtioInput.InputType
VirtioInputPointingDevice = "pointing"
VirtioInputKeyboardDevice = "keyboard"
// Options for VirtioGPUResolution
VirtioGPUResolutionWidth = "width"
VirtioGPUResolutionHeight = "height"
// Default VirtioGPU Resolution
defaultVirtioGPUResolutionWidth = 800
defaultVirtioGPUResolutionHeight = 600
)
// VirtioInput configures an input device, such as a keyboard or pointing device
// (mouse) that the virtual machine can use
type VirtioInput struct {
InputType string `json:"inputType"` // currently supports "pointing" and "keyboard" input types
}
type VirtioGPUResolution struct {
Width int `json:"width"`
Height int `json:"height"`
}
// VirtioGPU configures a GPU device, such as the host computer's display
type VirtioGPU struct {
UsesGUI bool `json:"usesGUI"`
VirtioGPUResolution
}
// VirtioVsock configures of a virtio-vsock device allowing 2-way communication
// between the host and the virtual machine type
type VirtioVsock struct {
// Port is the virtio-vsock port used for this device, see `man vsock` for more
// details.
Port uint32 `json:"port"`
// SocketURL is the path to a unix socket on the host to use for the virtio-vsock communication with the guest.
SocketURL string `json:"socketURL"`
// If true, vsock connections will have to be done from guest to host. If false, vsock connections will only be possible
// from host to guest
Listen bool `json:"listen,omitempty"`
}
// VirtioBlk configures a disk device.
type VirtioBlk struct {
DiskStorageConfig
DeviceIdentifier string `json:"deviceIdentifier,omitempty"`
}
type DirectorySharingConfig struct {
MountTag string `json:"mountTag"`
}
// VirtioFs configures directory sharing between the guest and the host.
type VirtioFs struct {
DirectorySharingConfig
SharedDir string `json:"sharedDir"`
}
// RosettaShare configures rosetta support in the guest to run Intel binaries on Apple CPUs
type RosettaShare struct {
DirectorySharingConfig
InstallRosetta bool `json:"installRosetta"`
IgnoreIfMissing bool `json:"ignoreIfMissing"`
}
// NVMExpressController configures a NVMe controller in the guest
type NVMExpressController struct {
DiskStorageConfig
}
// VirtioRng configures a random number generator (RNG) device.
type VirtioRng struct {
}
// TODO: Add BridgedNetwork support
// https://github.com/Code-Hex/vz/blob/d70a0533bf8ed0fa9ab22fa4d4ca554b7c3f3ce5/network.go#L81-L82
// VirtioNet configures the virtual machine networking.
type VirtioNet struct {
Nat bool `json:"nat"`
MacAddress net.HardwareAddr `json:"-"` // custom marshaller in json.go
// file parameter is holding a connected datagram socket.
// see https://github.com/Code-Hex/vz/blob/7f648b6fb9205d6f11792263d79876e3042c33ec/network.go#L113-L155
Socket *os.File `json:"socket,omitempty"`
UnixSocketPath string `json:"unixSocketPath,omitempty"`
}
// VirtioSerial configures the virtual machine serial ports.
type VirtioSerial struct {
LogFile string `json:"logFile,omitempty"`
UsesStdio bool `json:"usesStdio,omitempty"`
}
type NBDSynchronizationMode string
const (
SynchronizationFullMode NBDSynchronizationMode = "full"
SynchronizationNoneMode NBDSynchronizationMode = "none"
)
type NetworkBlockDevice struct {
NetworkBlockStorageConfig
DeviceIdentifier string
Timeout time.Duration
SynchronizationMode NBDSynchronizationMode
}
type VirtioBalloon struct{}
func VirtioBalloonNew() (VirtioDevice, error) {
return &VirtioBalloon{}, nil
}
func (v *VirtioBalloon) FromOptions(options []option) error {
if len(options) != 0 {
return fmt.Errorf("unknown options for virtio-balloon devices: %s", options)
}
return nil
}
func (v *VirtioBalloon) ToCmdLine() ([]string, error) {
return []string{"--device", "virtio-balloon"}, nil
}
type option struct {
key string
value string
}
func strToOption(str string) option {
splitStr := strings.SplitN(str, "=", 2)
opt := option{
key: splitStr[0],
}
if len(splitStr) > 1 {
opt.value = splitStr[1]
}
return opt
}
func strvToOptions(opts []string) []option {
parsedOpts := []option{}
for _, opt := range opts {
if len(opt) == 0 {
continue
}
parsedOpts = append(parsedOpts, strToOption(opt))
}
return parsedOpts
}
func deviceFromCmdLine(deviceOpts string) (VirtioDevice, error) {
opts := strings.Split(deviceOpts, ",")
if len(opts) == 0 {
return nil, fmt.Errorf("empty option list in command line argument")
}
var dev VirtioDevice
switch opts[0] {
case "rosetta":
dev = &RosettaShare{}
case "nvme":
dev = nvmExpressControllerNewEmpty()
case "virtio-blk":
dev = virtioBlkNewEmpty()
case "virtio-fs":
dev = &VirtioFs{}
case "virtio-net":
dev = &VirtioNet{}
case "virtio-rng":
dev = &VirtioRng{}
case "virtio-serial":
dev = &VirtioSerial{}
case "virtio-vsock":
dev = &VirtioVsock{}
case "usb-mass-storage":
dev = usbMassStorageNewEmpty()
case "virtio-input":
dev = &VirtioInput{}
case "virtio-gpu":
dev = &VirtioGPU{}
case "virtio-balloon":
dev = &VirtioBalloon{}
case "nbd":
dev = networkBlockDeviceNewEmpty()
default:
return nil, fmt.Errorf("unknown device type: %s", opts[0])
}
parsedOpts := strvToOptions(opts[1:])
if err := dev.FromOptions(parsedOpts); err != nil {
return nil, err
}
return dev, nil
}
// VirtioSerialNew creates a new serial device for the virtual machine. The
// output the virtual machine sent to the serial port will be written to the
// file at logFilePath.
func VirtioSerialNew(logFilePath string) (VirtioDevice, error) {
return &VirtioSerial{
LogFile: logFilePath,
}, nil
}
func VirtioSerialNewStdio() (VirtioDevice, error) {
return &VirtioSerial{
UsesStdio: true,
}, nil
}
func (dev *VirtioSerial) validate() error {
if dev.LogFile != "" && dev.UsesStdio {
return fmt.Errorf("'logFilePath' and 'stdio' cannot be set at the same time")
}
if dev.LogFile == "" && !dev.UsesStdio {
return fmt.Errorf("one of 'logFilePath' or 'stdio' must be set")
}
return nil
}
func (dev *VirtioSerial) ToCmdLine() ([]string, error) {
if err := dev.validate(); err != nil {
return nil, err
}
if dev.UsesStdio {
return []string{"--device", "virtio-serial,stdio"}, nil
}
return []string{"--device", fmt.Sprintf("virtio-serial,logFilePath=%s", dev.LogFile)}, nil
}
func (dev *VirtioSerial) FromOptions(options []option) error {
for _, option := range options {
switch option.key {
case "logFilePath":
dev.LogFile = option.value
case "stdio":
if option.value != "" {
return fmt.Errorf("unexpected value for virtio-serial 'stdio' option: %s", option.value)
}
dev.UsesStdio = true
default:
return fmt.Errorf("unknown option for virtio-serial devices: %s", option.key)
}
}
return dev.validate()
}
// VirtioInputNew creates a new input device for the virtual machine.
// The inputType parameter is the type of virtio-input device that will be added
// to the machine.
func VirtioInputNew(inputType string) (VirtioDevice, error) {
dev := &VirtioInput{
InputType: inputType,
}
if err := dev.validate(); err != nil {
return nil, err
}
return dev, nil
}
func (dev *VirtioInput) validate() error {
if dev.InputType != VirtioInputPointingDevice && dev.InputType != VirtioInputKeyboardDevice {
return fmt.Errorf("unknown option for virtio-input devices: %s", dev.InputType)
}
return nil
}
func (dev *VirtioInput) ToCmdLine() ([]string, error) {
if err := dev.validate(); err != nil {
return nil, err
}
return []string{"--device", fmt.Sprintf("virtio-input,%s", dev.InputType)}, nil
}
func (dev *VirtioInput) FromOptions(options []option) error {
for _, option := range options {
switch option.key {
case VirtioInputPointingDevice, VirtioInputKeyboardDevice:
if option.value != "" {
return fmt.Errorf("unexpected value for virtio-input %s option: %s", option.key, option.value)
}
dev.InputType = option.key
default:
return fmt.Errorf("unknown option for virtio-input devices: %s", option.key)
}
}
return dev.validate()
}
// VirtioGPUNew creates a new gpu device for the virtual machine.
// The usesGUI parameter determines whether a graphical application window will
// be displayed
func VirtioGPUNew() (VirtioDevice, error) {
return &VirtioGPU{
UsesGUI: false,
VirtioGPUResolution: VirtioGPUResolution{
Width: defaultVirtioGPUResolutionWidth,
Height: defaultVirtioGPUResolutionHeight,
},
}, nil
}
func (dev *VirtioGPU) validate() error {
if dev.Height < 1 || dev.Width < 1 {
return fmt.Errorf("invalid dimensions for virtio-gpu device resolution: %dx%d", dev.Width, dev.Height)
}
return nil
}
func (dev *VirtioGPU) ToCmdLine() ([]string, error) {
if err := dev.validate(); err != nil {
return nil, err
}
return []string{"--device", fmt.Sprintf("virtio-gpu,width=%d,height=%d", dev.Width, dev.Height)}, nil
}
func (dev *VirtioGPU) FromOptions(options []option) error {
for _, option := range options {
switch option.key {
case VirtioGPUResolutionHeight:
height, err := strconv.Atoi(option.value)
if err != nil || height < 1 {
return fmt.Errorf("Invalid value for virtio-gpu %s: %s", option.key, option.value)
}
dev.Height = height
case VirtioGPUResolutionWidth:
width, err := strconv.Atoi(option.value)
if err != nil || width < 1 {
return fmt.Errorf("Invalid value for virtio-gpu %s: %s", option.key, option.value)
}
dev.Width = width
default:
return fmt.Errorf("unknown option for virtio-gpu devices: %s", option.key)
}
}
if dev.Width == 0 && dev.Height == 0 {
dev.Width = defaultVirtioGPUResolutionWidth
dev.Height = defaultVirtioGPUResolutionHeight
}
return dev.validate()
}
// VirtioNetNew creates a new network device for the virtual machine. It will
// use macAddress as its MAC address.
func VirtioNetNew(macAddress string) (*VirtioNet, error) {
var hwAddr net.HardwareAddr
if macAddress != "" {
var err error
if hwAddr, err = net.ParseMAC(macAddress); err != nil {
return nil, err
}
}
return &VirtioNet{
Nat: true,
MacAddress: hwAddr,
}, nil
}
// SetSocket Set the socket to use for the network communication
//
// This maps the virtual machine network interface to a connected datagram
// socket. This means all network traffic on this interface will go through
// file.
// file must be a connected datagram (SOCK_DGRAM) socket.
func (dev *VirtioNet) SetSocket(file *os.File) {
dev.Socket = file
dev.Nat = false
}
func (dev *VirtioNet) SetUnixSocketPath(path string) {
dev.UnixSocketPath = path
dev.Nat = false
}
func (dev *VirtioNet) validate() error {
if dev.Nat && dev.Socket != nil {
return fmt.Errorf("'nat' and 'fd' cannot be set at the same time")
}
if dev.Nat && dev.UnixSocketPath != "" {
return fmt.Errorf("'nat' and 'unixSocketPath' cannot be set at the same time")
}
if dev.Socket != nil && dev.UnixSocketPath != "" {
return fmt.Errorf("'fd' and 'unixSocketPath' cannot be set at the same time")
}
if !dev.Nat && dev.Socket == nil && dev.UnixSocketPath == "" {
return fmt.Errorf("one of 'nat' or 'fd' or 'unixSocketPath' must be set")
}
return nil
}
func (dev *VirtioNet) ToCmdLine() ([]string, error) {
if err := dev.validate(); err != nil {
return nil, err
}
builder := strings.Builder{}
builder.WriteString("virtio-net")
switch {
case dev.Nat:
builder.WriteString(",nat")
case dev.UnixSocketPath != "":
fmt.Fprintf(&builder, ",unixSocketPath=%s", dev.UnixSocketPath)
default:
fmt.Fprintf(&builder, ",fd=%d", dev.Socket.Fd())
}
if len(dev.MacAddress) != 0 {
builder.WriteString(fmt.Sprintf(",mac=%s", dev.MacAddress))
}
return []string{"--device", builder.String()}, nil
}
func (dev *VirtioNet) FromOptions(options []option) error {
for _, option := range options {
switch option.key {
case "nat":
if option.value != "" {
return fmt.Errorf("unexpected value for virtio-net 'nat' option: %s", option.value)
}
dev.Nat = true
case "mac":
macAddress, err := net.ParseMAC(option.value)
if err != nil {
return err
}
dev.MacAddress = macAddress
case "fd":
fd, err := strconv.Atoi(option.value)
if err != nil {
return err
}
dev.Socket = os.NewFile(uintptr(fd), "vfkit virtio-net socket")
case "unixSocketPath":
dev.UnixSocketPath = option.value
default:
return fmt.Errorf("unknown option for virtio-net devices: %s", option.key)
}
}
return dev.validate()
}
// VirtioRngNew creates a new random number generator device to feed entropy
// into the virtual machine.
func VirtioRngNew() (VirtioDevice, error) {
return &VirtioRng{}, nil
}
func (dev *VirtioRng) ToCmdLine() ([]string, error) {
return []string{"--device", "virtio-rng"}, nil
}
func (dev *VirtioRng) FromOptions(options []option) error {
if len(options) != 0 {
return fmt.Errorf("unknown options for virtio-rng devices: %s", options)
}
return nil
}
func nvmExpressControllerNewEmpty() *NVMExpressController {
return &NVMExpressController{
DiskStorageConfig: DiskStorageConfig{
StorageConfig: StorageConfig{
DevName: "nvme",
},
},
}
}
// NVMExpressControllerNew creates a new NVMExpress controller to use in the
// virtual machine. It will use the file at imagePath as the disk image. This
// image must be in raw format.
func NVMExpressControllerNew(imagePath string) (*NVMExpressController, error) {
r := nvmExpressControllerNewEmpty()
r.ImagePath = imagePath
return r, nil
}
func virtioBlkNewEmpty() *VirtioBlk {
return &VirtioBlk{
DiskStorageConfig: DiskStorageConfig{
StorageConfig: StorageConfig{
DevName: "virtio-blk",
},
},
DeviceIdentifier: "",
}
}
// VirtioBlkNew creates a new disk to use in the virtual machine. It will use
// the file at imagePath as the disk image. This image must be in raw format.
func VirtioBlkNew(imagePath string) (*VirtioBlk, error) {
virtioBlk := virtioBlkNewEmpty()
virtioBlk.ImagePath = imagePath
return virtioBlk, nil
}
func (dev *VirtioBlk) SetDeviceIdentifier(devID string) {
dev.DeviceIdentifier = devID
}
func (dev *VirtioBlk) FromOptions(options []option) error {
unhandledOpts := []option{}
for _, option := range options {
switch option.key {
case "deviceId":
dev.DeviceIdentifier = option.value
default:
unhandledOpts = append(unhandledOpts, option)
}
}
return dev.DiskStorageConfig.FromOptions(unhandledOpts)
}
func (dev *VirtioBlk) ToCmdLine() ([]string, error) {
cmdLine, err := dev.DiskStorageConfig.ToCmdLine()
if err != nil {
return []string{}, err
}
if len(cmdLine) != 2 {
return []string{}, fmt.Errorf("unexpected storage config commandline")
}
if dev.DeviceIdentifier != "" {
cmdLine[1] = fmt.Sprintf("%s,deviceId=%s", cmdLine[1], dev.DeviceIdentifier)
}
return cmdLine, nil
}
// VirtioVsockNew creates a new virtio-vsock device for 2-way communication
// between the host and the virtual machine. The communication will happen on
// vsock port, and on the host it will use the unix socket at socketURL.
// When listen is true, the host will be listening for connections over vsock.
// When listen is false, the guest will be listening for connections over vsock.
func VirtioVsockNew(port uint, socketURL string, listen bool) (VirtioDevice, error) {
if port > math.MaxUint32 {
return nil, fmt.Errorf("invalid vsock port: %d", port)
}
return &VirtioVsock{
Port: uint32(port), //#nosec G115 -- was compared to math.MaxUint32
SocketURL: socketURL,
Listen: listen,
}, nil
}
func (dev *VirtioVsock) ToCmdLine() ([]string, error) {
if dev.Port == 0 || dev.SocketURL == "" {
return nil, fmt.Errorf("virtio-vsock needs both a port and a socket URL")
}
var listenStr string
if dev.Listen {
listenStr = "listen"
} else {
listenStr = "connect"
}
return []string{"--device", fmt.Sprintf("virtio-vsock,port=%d,socketURL=%s,%s", dev.Port, dev.SocketURL, listenStr)}, nil
}
func (dev *VirtioVsock) FromOptions(options []option) error {
// default to listen for backwards compatibliity
dev.Listen = true
for _, option := range options {
switch option.key {
case "socketURL":
dev.SocketURL = option.value
case "port":
port, err := strconv.ParseUint(option.value, 10, 32)
if err != nil {
return err
}
dev.Port = uint32(port) //#nosec G115 -- ParseUint(_, _, 32) guarantees no overflow
case "listen":
dev.Listen = true
case "connect":
dev.Listen = false
default:
return fmt.Errorf("unknown option for virtio-vsock devices: %s", option.key)
}
}
return nil
}
// VirtioFsNew creates a new virtio-fs device for file sharing. It will share
// the directory at sharedDir with the virtual machine. This directory can be
// mounted in the VM using `mount -t virtiofs mountTag /some/dir`
func VirtioFsNew(sharedDir string, mountTag string) (VirtioDevice, error) {
return &VirtioFs{
DirectorySharingConfig: DirectorySharingConfig{
MountTag: mountTag,
},
SharedDir: sharedDir,
}, nil
}
func (dev *VirtioFs) ToCmdLine() ([]string, error) {
if dev.SharedDir == "" {
return nil, fmt.Errorf("virtio-fs needs the path to the directory to share")
}
if dev.MountTag != "" {
return []string{"--device", fmt.Sprintf("virtio-fs,sharedDir=%s,mountTag=%s", dev.SharedDir, dev.MountTag)}, nil
}
return []string{"--device", fmt.Sprintf("virtio-fs,sharedDir=%s", dev.SharedDir)}, nil
}
func (dev *VirtioFs) FromOptions(options []option) error {
for _, option := range options {
switch option.key {
case "sharedDir":
dev.SharedDir = option.value
case "mountTag":
dev.MountTag = option.value
default:
return fmt.Errorf("unknown option for virtio-fs devices: %s", option.key)
}
}
return nil
}
// RosettaShareNew RosettaShare creates a new rosetta share for running x86_64 binaries on M1 machines.
// It will share a directory containing the linux rosetta binaries with the
// virtual machine. This directory can be mounted in the VM using `mount -t
// virtiofs mountTag /some/dir`
func RosettaShareNew(mountTag string) (VirtioDevice, error) {
return &RosettaShare{
DirectorySharingConfig: DirectorySharingConfig{
MountTag: mountTag,
},
}, nil
}
func (dev *RosettaShare) ToCmdLine() ([]string, error) {
if dev.MountTag == "" {
return nil, fmt.Errorf("rosetta shares require a mount tag to be specified")
}
builder := strings.Builder{}
builder.WriteString("rosetta")
fmt.Fprintf(&builder, ",mountTag=%s", dev.MountTag)
if dev.InstallRosetta {
builder.WriteString(",install")
}
if dev.IgnoreIfMissing {
builder.WriteString(",ignore-if-missing")
}
return []string{"--device", builder.String()}, nil
}
func (dev *RosettaShare) FromOptions(options []option) error {
for _, option := range options {
switch option.key {
case "mountTag":
dev.MountTag = option.value
case "install":
dev.InstallRosetta = true
case "ignore-if-missing":
dev.IgnoreIfMissing = true
default:
return fmt.Errorf("unknown option for rosetta share: %s", option.key)
}
}
return nil
}
func networkBlockDeviceNewEmpty() *NetworkBlockDevice {
return &NetworkBlockDevice{
NetworkBlockStorageConfig: NetworkBlockStorageConfig{
StorageConfig: StorageConfig{
DevName: "nbd",
},
},
DeviceIdentifier: "",
Timeout: time.Duration(15000 * time.Millisecond), // set a default timeout to 15s
SynchronizationMode: SynchronizationFullMode, // default mode to full
}
}
// NetworkBlockDeviceNew creates a new disk by connecting to a remote Network Block Device (NBD) server.
// The provided uri must be in the format <scheme>://<address>/<export-name>
// where scheme could have any of these value: nbd, nbds, nbd+unix and nbds+unix.
// More info can be found at https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md
// This allows the virtual machine to access and use the remote storage as if it were a local disk.
func NetworkBlockDeviceNew(uri string, timeout uint32, synchronization NBDSynchronizationMode) (*NetworkBlockDevice, error) {
nbd := networkBlockDeviceNewEmpty()
nbd.URI = uri
nbd.Timeout = time.Duration(timeout) * time.Millisecond
nbd.SynchronizationMode = synchronization
return nbd, nil
}
func (nbd *NetworkBlockDevice) ToCmdLine() ([]string, error) {
cmdLine, err := nbd.NetworkBlockStorageConfig.ToCmdLine()
if err != nil {
return []string{}, err
}
if len(cmdLine) != 2 {
return []string{}, fmt.Errorf("unexpected storage config commandline")
}
if nbd.DeviceIdentifier != "" {
cmdLine[1] = fmt.Sprintf("%s,deviceId=%s", cmdLine[1], nbd.DeviceIdentifier)
}
if nbd.Timeout.Milliseconds() > 0 {
cmdLine[1] = fmt.Sprintf("%s,timeout=%d", cmdLine[1], nbd.Timeout.Milliseconds())
}
if nbd.SynchronizationMode == "none" || nbd.SynchronizationMode == "full" {
cmdLine[1] = fmt.Sprintf("%s,sync=%s", cmdLine[1], nbd.SynchronizationMode)
}
return cmdLine, nil
}
func (nbd *NetworkBlockDevice) FromOptions(options []option) error {
unhandledOpts := []option{}
for _, option := range options {
switch option.key {
case "deviceId":
nbd.DeviceIdentifier = option.value
case "timeout":
timeoutMS, err := strconv.ParseInt(option.value, 10, 32)
if err != nil {
return err
}
nbd.Timeout = time.Duration(timeoutMS) * time.Millisecond
case "sync":
switch option.value {
case string(SynchronizationFullMode):
nbd.SynchronizationMode = SynchronizationFullMode
case string(SynchronizationNoneMode):
nbd.SynchronizationMode = SynchronizationNoneMode
default:
return fmt.Errorf("invalid sync mode: %s, must be 'full' or 'none'", option.value)
}
default:
unhandledOpts = append(unhandledOpts, option)
}
}
return nbd.NetworkBlockStorageConfig.FromOptions(unhandledOpts)
}
type USBMassStorage struct {
DiskStorageConfig
}
func usbMassStorageNewEmpty() *USBMassStorage {
return &USBMassStorage{
DiskStorageConfig: DiskStorageConfig{
StorageConfig: StorageConfig{
DevName: "usb-mass-storage",
},
},
}
}
// USBMassStorageNew creates a new USB disk to use in the virtual machine. It will use
// the file at imagePath as the disk image. This image must be in raw or ISO format.
func USBMassStorageNew(imagePath string) (*USBMassStorage, error) {
usbMassStorage := usbMassStorageNewEmpty()
usbMassStorage.ImagePath = imagePath
return usbMassStorage, nil
}
func (dev *USBMassStorage) SetReadOnly(readOnly bool) {
dev.StorageConfig.ReadOnly = readOnly
}
// StorageConfig configures a disk device.
type StorageConfig struct {
DevName string `json:"devName"`
ReadOnly bool `json:"readOnly,omitempty"`
}
type DiskStorageConfig struct {
StorageConfig
ImagePath string `json:"imagePath,omitempty"`
}
type NetworkBlockStorageConfig struct {
StorageConfig
URI string `json:"uri,omitempty"`
}
func (config *DiskStorageConfig) ToCmdLine() ([]string, error) {
if config.ImagePath == "" {
return nil, fmt.Errorf("%s devices need the path to a disk image", config.DevName)
}
value := fmt.Sprintf("%s,path=%s", config.DevName, config.ImagePath)
if config.ReadOnly {
value += ",readonly"
}
return []string{"--device", value}, nil
}
func (config *DiskStorageConfig) FromOptions(options []option) error {
for _, option := range options {
switch option.key {
case "path":
config.ImagePath = option.value
case "readonly":
if option.value != "" {
return fmt.Errorf("unexpected value for virtio-blk 'readonly' option: %s", option.value)
}
config.ReadOnly = true
default:
return fmt.Errorf("unknown option for %s devices: %s", config.DevName, option.key)
}
}
return nil
}
func (config *NetworkBlockStorageConfig) ToCmdLine() ([]string, error) {
if config.URI == "" {
return nil, fmt.Errorf("%s devices need the uri to a remote block device", config.DevName)
}
value := fmt.Sprintf("%s,uri=%s", config.DevName, config.URI)
if config.ReadOnly {
value += ",readonly"
}
return []string{"--device", value}, nil
}
func (config *NetworkBlockStorageConfig) FromOptions(options []option) error {
for _, option := range options {
switch option.key {
case "uri":
config.URI = option.value
case "readonly":
if option.value != "" {
return fmt.Errorf("unexpected value for virtio-blk 'readonly' option: %s", option.value)
}
config.ReadOnly = true
default:
return fmt.Errorf("unknown option for %s devices: %s", config.DevName, option.key)
}
}
return nil
}