Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement sshfs volumes for podman machine #12584

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cmd/podman/machine/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ func init() {
flags.StringVar(&initOpts.ImagePath, ImagePathFlagName, cfg.Machine.Image, "Path to qcow image")
_ = initCmd.RegisterFlagCompletionFunc(ImagePathFlagName, completion.AutocompleteDefault)

VolumeFlagName := "volume"
flags.StringArrayVarP(&initOpts.Volumes, VolumeFlagName, "v", []string{}, "Volumes to mount, source:target")
_ = initCmd.RegisterFlagCompletionFunc(VolumeFlagName, completion.AutocompleteDefault)

VolumeDriverFlagName := "volume-driver"
flags.StringVar(&initOpts.VolumeDriver, VolumeDriverFlagName, "", "Optional volume driver")
_ = initCmd.RegisterFlagCompletionFunc(VolumeDriverFlagName, completion.AutocompleteDefault)

IgnitionPathFlagName := "ignition-path"
flags.StringVar(&initOpts.IgnitionPath, IgnitionPathFlagName, "", "Path to ignition file")
_ = initCmd.RegisterFlagCompletionFunc(IgnitionPathFlagName, completion.AutocompleteDefault)
Expand Down
15 changes: 15 additions & 0 deletions docs/source/markdown/podman-machine-init.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ Set the timezone for the machine and containers. Valid values are `local` or
a `timezone` such as `America/Chicago`. A value of `local`, which is the default,
means to use the timezone of the machine host.

#### **--volume**, **-v**=*source:target*

Mounts a volume from source to target.

Create a mount. If /host-dir:/machine-dir is specified as the `*source:target*`,
Podman mounts _host-dir_ in the host to _machine-dir_ in the Podman machine.

The root filesystem is mounted read-only in the default operating system,
so mounts must be created under the /mnt directory.

#### **--volume-driver**
afbjorklund marked this conversation as resolved.
Show resolved Hide resolved

Driver to use for mounting volumes from the host, such as `virtfs`.

#### **--help**

Print usage statement.
Expand All @@ -72,6 +86,7 @@ $ podman machine init
$ podman machine init myvm
$ podman machine init --disk-size 50
$ podman machine init --memory=1024 myvm
$ podman machine init -v /Users:/mnt/Users
```

## SEE ALSO
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/hpcloud/tail v1.0.0
github.com/json-iterator/go v1.1.12
github.com/lima-vm/sshocker v0.2.2
github.com/mattn/go-isatty v0.0.14
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
github.com/mrunalp/fileutils v0.5.0
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQ
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand All @@ -688,6 +689,8 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lima-vm/sshocker v0.2.2 h1:GnUMCuyTfXqXwtdJZz4oQ1hQiF32EF6zM7isBDL73Ik=
github.com/lima-vm/sshocker v0.2.2/go.mod h1:Kxd+2F7gIG/ZOqBpTonTHTUw6humfByUV1uzQb/OC7Q=
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
Expand Down Expand Up @@ -865,6 +868,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/sftp v1.13.3 h1:XFSVAvRDGUhzAJ8Ll0APzHx3NTCAnMGaAsd3yi+Oc9k=
github.com/pkg/sftp v1.13.3/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
Expand Down Expand Up @@ -1089,6 +1094,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
Expand Down Expand Up @@ -1312,6 +1318,7 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand All @@ -1324,6 +1331,7 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210818153620-00dd8d7831e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
2 changes: 2 additions & 0 deletions pkg/machine/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ type InitOptions struct {
DiskSize uint64
IgnitionPath string
ImagePath string
Volumes []string
VolumeDriver string
IsDefault bool
Memory uint64
Name string
Expand Down
10 changes: 10 additions & 0 deletions pkg/machine/qemu/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ type MachineVM struct {
CPUs uint64
// The command line representation of the qemu command
CmdLine []string
// Mounts is the list of remote filesystems to mount
Mounts []Mount
// IdentityPath is the fq path to the ssh priv key
IdentityPath string
// IgnitionFilePath is the fq path to the .ign file
Expand All @@ -33,6 +35,14 @@ type MachineVM struct {
RemoteUsername string
}

type Mount struct {
Type string
Tag string
Source string
Target string
ReadOnly bool
}

type Monitor struct {
// Address portion of the qmp monitor (/tmp/tmp.sock)
Address string
Expand Down
167 changes: 166 additions & 1 deletion pkg/machine/qemu/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import (
"github.com/containers/storage/pkg/homedir"
"github.com/digitalocean/go-qemu/qmp"
"github.com/docker/go-units"
"github.com/lima-vm/sshocker/pkg/mount"
"github.com/lima-vm/sshocker/pkg/ssh"
"github.com/lima-vm/sshocker/pkg/sshocker"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
Expand All @@ -36,6 +39,13 @@ func GetQemuProvider() machine.Provider {
return qemuProvider
}

const (
VolumeTypeVirtfs = "virtfs"
VolumeTypeSshfs = "sshfs"
MountType9p = "9p"
MountTypeSshfs = "sshfs"
)
afbjorklund marked this conversation as resolved.
Show resolved Hide resolved

// NewMachine initializes an instance of a virtual machine based on the qemu
// virtualization.
func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
Expand Down Expand Up @@ -167,6 +177,57 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
// Add arch specific options including image location
v.CmdLine = append(v.CmdLine, v.addArchOptions()...)

var volumeType string
switch opts.VolumeDriver {
case "virtfs":
volumeType = VolumeTypeVirtfs
case "sshfs":
volumeType = VolumeTypeSshfs
case "": // default driver
volumeType = VolumeTypeVirtfs
default:
err := fmt.Errorf("unknown volume driver: %s", opts.VolumeDriver)
return false, err
}

mounts := []Mount{}
for i, volume := range opts.Volumes {
tag := fmt.Sprintf("vol%d", i)
paths := strings.SplitN(volume, ":", 3)
source := paths[0]
target := source
readonly := false
if len(paths) > 1 {
target = paths[1]
}
if len(paths) > 2 {
options := paths[2]
volopts := strings.Split(options, ",")
for _, o := range volopts {
switch o {
case "rw":
readonly = false
case "ro":
readonly = true
default:
fmt.Printf("Unknown option: %s\n", o)
}
}
}
switch volumeType {
case VolumeTypeVirtfs:
virtfsOptions := fmt.Sprintf("local,path=%s,mount_tag=%s,security_model=mapped-xattr", source, tag)
if readonly {
virtfsOptions += ",readonly"
}
v.CmdLine = append(v.CmdLine, []string{"-virtfs", virtfsOptions}...)
mounts = append(mounts, Mount{Type: MountType9p, Tag: tag, Source: source, Target: target, ReadOnly: readonly})
case VolumeTypeSshfs:
mounts = append(mounts, Mount{Type: MountTypeSshfs, Tag: tag, Source: source, Target: target, ReadOnly: readonly})
}
}
v.Mounts = mounts

// Add location of bootable image
v.CmdLine = append(v.CmdLine, "-drive", "if=virtio,file="+v.ImagePath)
// This kind of stinks but no other way around this r/n
Expand Down Expand Up @@ -329,7 +390,48 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return err
}
_, err = bufio.NewReader(conn).ReadString('\n')
return err
if err != nil {
return err
}

if len(v.Mounts) > 0 {
for !v.isRunning() || !v.isListening() {
time.Sleep(100 * time.Millisecond)
}
}
for _, mount := range v.Mounts {
fmt.Printf("Mounting volume... %s:%s\n", mount.Source, mount.Target)
// create mountpoint directory if it doesn't exist
err = v.SSH(name, machine.SSHOptions{Args: []string{"-q", "--", "sudo", "mkdir", "-p", mount.Target}})
if err != nil {
return err
}
switch mount.Type {
case MountType9p:
mountOptions := []string{"-t", "9p"}
mountOptions = append(mountOptions, []string{"-o", "trans=virtio", mount.Tag, mount.Target}...)
mountOptions = append(mountOptions, []string{"-o", "version=9p2000.L,msize=131072"}...)
if mount.ReadOnly {
mountOptions = append(mountOptions, []string{"-o", "ro"}...)
}
err = v.SSH(name, machine.SSHOptions{Args: append([]string{"-q", "--", "sudo", "mount"}, mountOptions...)})
if err != nil {
return err
}
case MountTypeSshfs:
err = v.SSH(name, machine.SSHOptions{Args: []string{"-q", "--", "sudo", "chown", v.RemoteUsername, mount.Target}})
if err != nil {
return err
}
err = v.SSHocker(name, mount.Source, mount.Target, mount.ReadOnly)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown mount type: %s", mount.Type)
}
}
return nil
}

// Stop uses the qmp monitor to call a system_powerdown
Expand Down Expand Up @@ -506,6 +608,16 @@ func (v *MachineVM) isRunning() bool {
return true
}

func (v *MachineVM) isListening() bool {
// Check if we can dial it
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "localhost", v.Port), 10*time.Millisecond)
if err != nil {
return false
}
conn.Close()
return true
}

// SSH opens an interactive SSH session to the vm specified.
// Added ssh function to VM interface: pkg/machine/config/go : line 58
func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error {
Expand Down Expand Up @@ -538,6 +650,59 @@ func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error {
return cmd.Run()
}

func (v *MachineVM) sshConfig() string {
config := fmt.Sprintf("Host %s\n", v.Name)
config += fmt.Sprintf(" IdentityFile %s\n", v.IdentityPath)
config += fmt.Sprintf(" User %s\n", v.RemoteUsername)
config += fmt.Sprintf(" Hostname %s\n", "localhost")
config += fmt.Sprintf(" Port %d\n", v.Port)
config += fmt.Sprintf(" UserKnownHostsFile %s\n", "/dev/null")
config += fmt.Sprintf(" StrictHostKeyChecking %s\n", "no")
return config
}

// SSHocker does a reverse sshfs mount over a SSH connection.
func (v *MachineVM) SSHocker(name string, source, target string, readonly bool) error {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return err
}
config := v.sshConfig()
sshConfigFile := filepath.Join(vmConfigDir, v.Name+".config")
err = ioutil.WriteFile(sshConfigFile, []byte(config), 0666)
if err != nil {
return err
}
sshConfig := &ssh.SSHConfig{
ConfigFile: sshConfigFile,
Persist: true,
}

if strings.HasPrefix(source, "~") {
source = strings.Replace(source, "~", homedir.Get(), 1)
}
if strings.HasPrefix(target, "/mnt") {
target = "/var" + target // special case for Fedora CoreOS
}
m := mount.Mount{
Type: mount.MountTypeReverseSSHFS,
Source: source,
Destination: target,
Readonly: readonly,
}

x := &sshocker.Sshocker{
SSHConfig: sshConfig,
Mounts: []mount.Mount{m},
Host: v.Name,
Port: v.Port,
Command: []string{"sleep", "36500d"}, // 100y
SSHFSAdditionalArgs: []string{},
}
logrus.Debugf("Executing: sshocker %v\n", x)
return x.Run()
}

// executes qemu-image info to get the virtual disk size
// of the diskimage
func getDiskSize(path string) (uint64, error) {
Expand Down
12 changes: 12 additions & 0 deletions vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,15 @@ github.com/klauspost/compress/zstd
github.com/klauspost/compress/zstd/internal/xxhash
# github.com/klauspost/pgzip v1.2.5
github.com/klauspost/pgzip
# github.com/kr/fs v0.1.0
github.com/kr/fs
# github.com/lima-vm/sshocker v0.2.2
## explicit
github.com/lima-vm/sshocker/pkg/mount
github.com/lima-vm/sshocker/pkg/reversesshfs
github.com/lima-vm/sshocker/pkg/ssh
github.com/lima-vm/sshocker/pkg/sshocker
github.com/lima-vm/sshocker/pkg/util
# github.com/manifoldco/promptui v0.9.0
github.com/manifoldco/promptui
github.com/manifoldco/promptui/list
Expand Down Expand Up @@ -588,6 +597,9 @@ github.com/ostreedev/ostree-go/pkg/otbuiltin
# github.com/pkg/errors v0.9.1
## explicit
github.com/pkg/errors
# github.com/pkg/sftp v1.13.3
github.com/pkg/sftp
github.com/pkg/sftp/internal/encoding/ssh/filexfer
# github.com/pmezard/go-difflib v1.0.0
## explicit
github.com/pmezard/go-difflib/difflib
Expand Down