148 lines
4.3 KiB
Go
148 lines
4.3 KiB
Go
/*
|
|
Copyright 2025 The Kubernetes Authors All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package virtiofs
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/docker/machine/libmachine/drivers"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Mount is a directory on the host shared with the guest using virtiofs.
|
|
type Mount struct {
|
|
// HostPath is an absolute path to existing directory to share with the
|
|
// guest via virtiofs protocol. Also called "source" by some tools.
|
|
HostPath string
|
|
|
|
// GuestPath is a path in the guest for mounting the shared directory using
|
|
// virtiofs. Also called target or mountpoint by some tools.
|
|
GuestPath string
|
|
|
|
// Tag is a string identifying the shared file system in the guest.
|
|
// Generated by minikube.
|
|
Tag string
|
|
}
|
|
|
|
// ValidateMountString parses the mount-string flag and validates that the
|
|
// specified paths can be used for virtiofs mount. Returns list with one
|
|
// validated mount, ready for configuring the driver.
|
|
// TODO: Drop when we have a flag supporting multiple mounts.
|
|
func ValidateMountString(s string) ([]*Mount, error) {
|
|
if s == "" {
|
|
return nil, nil
|
|
}
|
|
return validateMounts([]string{s})
|
|
}
|
|
|
|
func validateMounts(args []string) ([]*Mount, error) {
|
|
var mounts []*Mount
|
|
|
|
seenHost := map[string]*Mount{}
|
|
seenGuest := map[string]*Mount{}
|
|
|
|
for _, s := range args {
|
|
mount, err := ParseMount(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := mount.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if existing, ok := seenHost[mount.HostPath]; ok {
|
|
return nil, fmt.Errorf("host path %q is already shared at guest path %q", mount.HostPath, existing.GuestPath)
|
|
}
|
|
seenHost[mount.HostPath] = mount
|
|
|
|
if existing, ok := seenGuest[mount.GuestPath]; ok {
|
|
return nil, fmt.Errorf("guest path %q is already shared from host path %q", mount.GuestPath, existing.HostPath)
|
|
}
|
|
seenGuest[mount.GuestPath] = mount
|
|
|
|
mounts = append(mounts, mount)
|
|
}
|
|
|
|
return mounts, nil
|
|
}
|
|
|
|
// ParseMount parses a string in the format "/host-path:/guest-path" and returns
|
|
// a new Mount instance. The mount must be validated before using it to
|
|
// configure the driver.
|
|
func ParseMount(s string) (*Mount, error) {
|
|
pair := strings.SplitN(s, ":", 2)
|
|
if len(pair) != 2 {
|
|
return nil, fmt.Errorf("invalid virtiofs mount %q: (expected '/host-path:/guest-path')", s)
|
|
}
|
|
|
|
return &Mount{
|
|
HostPath: pair[0],
|
|
GuestPath: pair[1],
|
|
Tag: uuid.NewString(),
|
|
}, nil
|
|
}
|
|
|
|
// Validate that the mount can be used for virtiofs device configuration. Both
|
|
// host and guest paths must be absolute. Host path must be a directory and must
|
|
// not include virtiofs configuration separator (",").
|
|
func (m *Mount) Validate() error {
|
|
// "," is a --device configuration separator in vfkit and krunkit.
|
|
if strings.Contains(m.HostPath, ",") {
|
|
return fmt.Errorf("host path %q must not contain ','", m.HostPath)
|
|
}
|
|
|
|
if !filepath.IsAbs(m.HostPath) {
|
|
return fmt.Errorf("host path %q is not an absolute path", m.HostPath)
|
|
}
|
|
|
|
if fs, err := os.Stat(m.HostPath); err != nil {
|
|
return fmt.Errorf("failed to validate host path %q: %w", m.HostPath, err)
|
|
} else if !fs.IsDir() {
|
|
return fmt.Errorf("host path %q is not a directory", m.HostPath)
|
|
}
|
|
|
|
if !filepath.IsAbs(m.GuestPath) {
|
|
return fmt.Errorf("guest path %q is not an absolute path", m.GuestPath)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetupMounts connects to the host via SSH, creates the mount directory if
|
|
// needed, and mount the virtiofs file system. It should be called by
|
|
// driver.Start().
|
|
func SetupMounts(d drivers.Driver, mounts []*Mount) error {
|
|
var script strings.Builder
|
|
|
|
script.WriteString("set -e\n")
|
|
|
|
for _, mount := range mounts {
|
|
script.WriteString(fmt.Sprintf("sudo mkdir -p \"%s\"\n", mount.GuestPath))
|
|
script.WriteString(fmt.Sprintf("sudo mount -t virtiofs %s \"%s\"\n", mount.Tag, mount.GuestPath))
|
|
}
|
|
|
|
if _, err := drivers.RunSSHCommandFromDriver(d, script.String()); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|