Permalink
Browse files

Add mount command for mounting machine directories

This comes in handy, when wanting to use volumes with drivers that
don't support folder sharing (such as KVM, as opposed to VirtualBox)

You will need FUSE and SSHFS installed, in order to use this feature.
The machine will also need a "sftp" binary, but most of them have it.

Signed-off-by: Anders F Björklund <anders.f.bjorklund@gmail.com>
  • Loading branch information...
afbjorklund authored and shin- committed Mar 6, 2017
1 parent 47068ae commit eb6af9c7401441b339bb4f551c18a61dff1f7459
Showing with 234 additions and 0 deletions.
  1. +2 −0 Dockerfile
  2. +12 −0 commands/commands.go
  3. +136 −0 commands/mount.go
  4. +84 −0 commands/mount_test.go
View
@@ -3,6 +3,8 @@ FROM golang:1.8.3
RUN apt-get update && apt-get install -y --no-install-recommends \
openssh-client \
rsync \
fuse \
sshfs \
&& rm -rf /var/lib/apt/lists/*
RUN go get github.com/golang/lint/golint \
View
@@ -361,6 +361,18 @@ var Commands = []cli.Command{
},
},
},
{
Name: "mount",
Usage: "Mount or unmount a directory from a machine with SSHFS.",
Description: "Arguments are [machine:][path] [mountpoint]",
Action: runCommand(cmdMount),
Flags: []cli.Flag{
cli.BoolFlag{
Name: "unmount, u",
Usage: "Unmount instead of mount",
},
},
},
{
Name: "start",
Usage: "Start a machine",
View
@@ -0,0 +1,136 @@
package commands
import (
"errors"
"fmt"
"os"
"os/exec"
"strings"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/log"
)
var (
// TODO: possibly move this to ssh package
baseSSHFSArgs = []string{
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "LogLevel=quiet", // suppress "Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts."
}
)
func cmdMount(c CommandLine, api libmachine.API) error {
args := c.Args()
if len(args) < 1 || len(args) > 2 {
c.ShowHelp()
return errWrongNumberArguments
}
src := args[0]
dest := ""
if len(args) > 1 {
dest = args[1]
}
hostInfoLoader := &storeHostInfoLoader{api}
cmd, err := getMountCmd(src, dest, c.Bool("unmount"), hostInfoLoader)
if err != nil {
return err
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func getMountCmd(src, dest string, unmount bool, hostInfoLoader HostInfoLoader) (*exec.Cmd, error) {
var cmdPath string
var err error
if !unmount {
cmdPath, err = exec.LookPath("sshfs")
if err != nil {
return nil, errors.New("You must have a copy of the sshfs binary locally to use the mount feature")
}
} else {
cmdPath, err = exec.LookPath("fusermount")
if err != nil {
return nil, errors.New("You must have a copy of the fusermount binary locally to use the unmount option")
}
}
srcHost, srcUser, srcPath, srcOpts, err := getInfoForSshfsArg(src, hostInfoLoader)
if err != nil {
return nil, err
}
if dest == "" {
dest = srcPath
}
sshArgs := baseSSHFSArgs
if srcHost.GetSSHKeyPath() != "" {
sshArgs = append(sshArgs, "-o", "IdentitiesOnly=yes")
}
// Append needed -i / private key flags to command.
sshArgs = append(sshArgs, srcOpts...)
// Append actual arguments for the sshfs command (i.e. docker@<ip>:/path)
locationArg, err := generateLocationArg(srcHost, srcUser, srcPath)
if err != nil {
return nil, err
}
if !unmount {
sshArgs = append(sshArgs, locationArg)
sshArgs = append(sshArgs, dest)
} else {
sshArgs = []string{"-u"}
sshArgs = append(sshArgs, dest)
}
cmd := exec.Command(cmdPath, sshArgs...)
log.Debug(*cmd)
return cmd, nil
}
func getInfoForSshfsArg(hostAndPath string, hostInfoLoader HostInfoLoader) (h HostInfo, user string, path string, args []string, err error) {
// Path with hostname. e.g. "hostname:/usr/bin/cmatrix"
var hostName string
if parts := strings.SplitN(hostAndPath, ":", 2); len(parts) < 2 {
hostName = defaultMachineName
path = parts[0]
} else {
hostName = parts[0]
path = parts[1]
}
if hParts := strings.SplitN(hostName, "@", 2); len(hParts) == 2 {
user, hostName = hParts[0], hParts[1]
}
// Remote path
h, err = hostInfoLoader.load(hostName)
if err != nil {
return nil, "", "", nil, fmt.Errorf("Error loading host: %s", err)
}
args = []string{}
port, err := h.GetSSHPort()
if err == nil && port > 0 {
args = append(args, "-o", fmt.Sprintf("Port=%v", port))
}
if h.GetSSHKeyPath() != "" {
args = append(args, "-o", fmt.Sprintf("IdentityFile=%s", h.GetSSHKeyPath()))
}
if user == "" {
user = h.GetSSHUsername()
}
return
}
View
@@ -0,0 +1,84 @@
package commands
import (
"os/exec"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetMountCmd(t *testing.T) {
hostInfoLoader := MockHostInfoLoader{MockHostInfo{
ip: "12.34.56.78",
sshPort: 234,
sshUsername: "root",
sshKeyPath: "/fake/keypath/id_rsa",
}}
path, err := exec.LookPath("sshfs")
if err != nil {
t.Skip("sshfs not found (install sshfs ?)")
}
cmd, err := getMountCmd("myfunhost:/home/docker/foo", "/tmp/foo", false, &hostInfoLoader)
expectedArgs := append(
baseSSHFSArgs,
"-o",
"IdentitiesOnly=yes",
"-o",
"Port=234",
"-o",
"IdentityFile=/fake/keypath/id_rsa",
"root@12.34.56.78:/home/docker/foo",
"/tmp/foo",
)
expectedCmd := exec.Command(path, expectedArgs...)
assert.Equal(t, expectedCmd, cmd)
assert.NoError(t, err)
}
func TestGetMountCmdWithoutSshKey(t *testing.T) {
hostInfoLoader := MockHostInfoLoader{MockHostInfo{
ip: "1.2.3.4",
sshUsername: "user",
}}
path, err := exec.LookPath("sshfs")
if err != nil {
t.Skip("sshfs not found (install sshfs ?)")
}
cmd, err := getMountCmd("myfunhost:/home/docker/foo", "", false, &hostInfoLoader)
expectedArgs := append(
baseSSHFSArgs,
"user@1.2.3.4:/home/docker/foo",
"/home/docker/foo",
)
expectedCmd := exec.Command(path, expectedArgs...)
assert.Equal(t, expectedCmd, cmd)
assert.NoError(t, err)
}
func TestGetMountCmdUnmount(t *testing.T) {
hostInfoLoader := MockHostInfoLoader{MockHostInfo{
ip: "1.2.3.4",
sshUsername: "user",
}}
path, err := exec.LookPath("fusermount")
if err != nil {
t.Skip("fusermount not found (install fuse ?)")
}
cmd, err := getMountCmd("myfunhost:/home/docker/foo", "/tmp/foo", true, &hostInfoLoader)
expectedArgs := []string{
"-u",
"/tmp/foo",
}
expectedCmd := exec.Command(path, expectedArgs...)
assert.Equal(t, expectedCmd, cmd)
assert.NoError(t, err)
}

0 comments on commit eb6af9c

Please sign in to comment.