diff --git a/cmd/run.go b/cmd/run.go index fbc83740..b23ff6b2 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -28,6 +28,7 @@ type osVmConfig struct { RemoveVm bool // Kill the running VM when it exits RemoveDiskImage bool // After exit of the VM, remove the disk image Quiet bool + BindMounts []string } var ( @@ -58,6 +59,7 @@ func init() { runCmd.Flags().BoolVar(&vmConfig.Quiet, "quiet", false, "Suppress output from bootc disk creation and VM boot console") runCmd.Flags().StringVar(&diskImageConfigInstance.RootSizeMax, "root-size-max", "", "Maximum size of root filesystem in bytes; optionally accepts M, G, T suffixes") runCmd.Flags().StringVar(&diskImageConfigInstance.DiskSize, "disk-size", "", "Allocate a disk image of this size in bytes; optionally accepts M, G, T suffixes") + runCmd.Flags().StringArrayVar(&vmConfig.BindMounts, "bind", nil, "Create a virtiofs mount between host and guest, separated by a `:`") } func doRun(flags *cobra.Command, args []string) error { @@ -141,6 +143,7 @@ func doRun(flags *cobra.Command, args []string) error { CloudInitDir: vmConfig.CloudInitDir, NoCredentials: vmConfig.NoCredentials, CloudInitData: flags.Flags().Changed("cloudinit"), + BindMounts: vmConfig.BindMounts, RemoveVm: vmConfig.RemoveVm, Background: vmConfig.Background, SSHPort: sshPort, diff --git a/pkg/vm/domain-template.xml b/pkg/vm/domain-template.xml index 33388cee..bf431044 100644 --- a/pkg/vm/domain-template.xml +++ b/pkg/vm/domain-template.xml @@ -17,6 +17,8 @@ hvm + + @@ -33,6 +35,7 @@ {{.CloudInitCDRom}} + {{.Filesystems}} diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index b9529681..1a32f0c3 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -62,6 +62,7 @@ type RunVMParameters struct { Cmd []string RemoveVm bool Background bool + BindMounts []string } type BootcVM interface { @@ -97,6 +98,7 @@ type BootcVMCommon struct { hasCloudInit bool cloudInitDir string cloudInitArgs string + BindMounts []string bootcDisk bootc.BootcDisk cacheDirLock utils.CacheLock } diff --git a/pkg/vm/vm_darwin.go b/pkg/vm/vm_darwin.go index b381333c..9d777b82 100644 --- a/pkg/vm/vm_darwin.go +++ b/pkg/vm/vm_darwin.go @@ -25,6 +25,10 @@ func NewVM(params NewVMParameters) (vm *BootcVMMac, err error) { return nil, fmt.Errorf("image ID is required") } + if len(params.BindMounts) > 0 { + return fmt.Errorf("bind mounts are currently not supported on this platform") + } + longId, cacheDir, err := GetVMCachePath(params.ImageID, params.User) if err != nil { return nil, fmt.Errorf("unable to get VM cache path: %w", err) diff --git a/pkg/vm/vm_linux.go b/pkg/vm/vm_linux.go index 5e8f10c0..5f888937 100644 --- a/pkg/vm/vm_linux.go +++ b/pkg/vm/vm_linux.go @@ -7,6 +7,7 @@ import ( "fmt" "path/filepath" "strconv" + "strings" "text/template" "time" @@ -120,6 +121,7 @@ func (v *BootcVMLinux) Run(params RunVMParameters) (err error) { v.cmd = params.Cmd v.hasCloudInit = params.CloudInitData v.cloudInitDir = params.CloudInitDir + v.BindMounts = params.BindMounts v.vmUsername = params.VMUser v.sshIdentity = params.SSHIdentity @@ -192,6 +194,7 @@ func (v *BootcVMLinux) parseDomainTemplate() (domainXML string, err error) { Name string CloudInitCDRom string CloudInitSMBios string + Filesystems string } templateParams := TemplateParams{ @@ -230,6 +233,31 @@ func (v *BootcVMLinux) parseDomainTemplate() (domainXML string, err error) { `, v.cloudInitArgs) } + bindMounts := "" + for _, mnt := range v.BindMounts { + parts := strings.SplitN(mnt, ":", 2) + if len(parts) < 2 { + return "", fmt.Errorf("invalid bind mount: %q", mnt) + } + src := parts[0] + absSrc, err := filepath.Abs(src) + if err != nil { + return "", fmt.Errorf("failed to resolve bind mount source %q", src) + } + dst := parts[1] + // Note we're not properly quoting for XML here, just relying + // on the Go quoting. Fixing that would involve pulling in a real + // XML library. + bindMounts += fmt.Sprintf(` + + + + + + `, absSrc, dst) + } + templateParams.Filesystems = bindMounts + err = tmpl.Execute(&domainXMLBuf, templateParams) if err != nil { return "", fmt.Errorf("unable to execute domain template: %w", err)