diff --git a/cmd/minikube/cmd/mount.go b/cmd/minikube/cmd/mount.go index 1bc8dfb4f3ac..1e45695d5c4c 100644 --- a/cmd/minikube/cmd/mount.go +++ b/cmd/minikube/cmd/mount.go @@ -31,6 +31,7 @@ import ( "github.com/spf13/cobra" "k8s.io/klog/v2" "k8s.io/minikube/pkg/minikube/cluster" + "k8s.io/minikube/pkg/minikube/constants" "k8s.io/minikube/pkg/minikube/detect" "k8s.io/minikube/pkg/minikube/driver" "k8s.io/minikube/pkg/minikube/exit" @@ -43,11 +44,30 @@ import ( const ( // nineP is the value of --type used for the 9p filesystem. - nineP = "9p" - defaultMountVersion = "9p2000.L" - defaultMsize = 262144 + nineP = "9p" + defaultMount9PVersion = "9p2000.L" + mount9PVersionDescription = "Specify the 9p version that the mount should use" + defaultMountGID = "docker" + mountGIDDescription = "Default group id used for the mount" + defaultMountIP = "" + mountIPDescription = "Specify the ip that the mount should be setup on" + defaultMountMode = 0o755 + mountModeDescription = "File permissions used for the mount" + defaultMountMSize = 262144 + mountMSizeDescription = "The number of bytes to use for 9p packet payload" + mountOptionsDescription = "Additional mount options, such as cache=fscache" + defaultMountPort = 0 + mountPortDescription = "Specify the port that the mount should be setup on, where 0 means any free port." + defaultMountType = nineP + mountTypeDescription = "Specify the mount filesystem type (supported types: 9p)" + defaultMountUID = "docker" + mountUIDDescription = "Default user id used for the mount" ) +func defaultMountOptions() []string { + return []string{} +} + // placeholders for flag values var ( mountIP string @@ -218,16 +238,16 @@ var mountCmd = &cobra.Command{ } func init() { - mountCmd.Flags().StringVar(&mountIP, "ip", "", "Specify the ip that the mount should be setup on") - mountCmd.Flags().Uint16Var(&mountPort, "port", 0, "Specify the port that the mount should be setup on, where 0 means any free port.") - mountCmd.Flags().StringVar(&mountType, "type", nineP, "Specify the mount filesystem type (supported types: 9p)") - mountCmd.Flags().StringVar(&mountVersion, "9p-version", defaultMountVersion, "Specify the 9p version that the mount should use") + mountCmd.Flags().StringVar(&mountIP, constants.MountIPFlag, defaultMountIP, mountIPDescription) + mountCmd.Flags().Uint16Var(&mountPort, constants.MountPortFlag, defaultMountPort, mountPortDescription) + mountCmd.Flags().StringVar(&mountType, constants.MountTypeFlag, defaultMountType, mountTypeDescription) + mountCmd.Flags().StringVar(&mountVersion, constants.Mount9PVersionFlag, defaultMount9PVersion, mount9PVersionDescription) mountCmd.Flags().BoolVar(&isKill, "kill", false, "Kill the mount process spawned by minikube start") - mountCmd.Flags().StringVar(&uid, "uid", "docker", "Default user id used for the mount") - mountCmd.Flags().StringVar(&gid, "gid", "docker", "Default group id used for the mount") - mountCmd.Flags().UintVar(&mode, "mode", 0o755, "File permissions used for the mount") - mountCmd.Flags().StringSliceVar(&options, "options", []string{}, "Additional mount options, such as cache=fscache") - mountCmd.Flags().IntVar(&mSize, "msize", defaultMsize, "The number of bytes to use for 9p packet payload") + mountCmd.Flags().StringVar(&uid, constants.MountUIDFlag, defaultMountUID, mountUIDDescription) + mountCmd.Flags().StringVar(&gid, constants.MountGIDFlag, defaultMountGID, mountGIDDescription) + mountCmd.Flags().UintVar(&mode, constants.MountModeFlag, defaultMountMode, mountModeDescription) + mountCmd.Flags().StringSliceVar(&options, constants.MountOptionsFlag, defaultMountOptions(), mountOptionsDescription) + mountCmd.Flags().IntVar(&mSize, constants.MountMSizeFlag, defaultMountMSize, mountMSizeDescription) } // getPort uses the requested port or asks the kernel for a free open port that is ready to use diff --git a/cmd/minikube/cmd/start_flags.go b/cmd/minikube/cmd/start_flags.go index 50ad7b877a9c..a86ad5b9bfcb 100644 --- a/cmd/minikube/cmd/start_flags.go +++ b/cmd/minikube/cmd/start_flags.go @@ -83,6 +83,15 @@ const ( imageRepository = "image-repository" imageMirrorCountry = "image-mirror-country" mountString = "mount-string" + mount9PVersion = "mount-9p-version" + mountGID = "mount-gid" + mountIPFlag = "mount-ip" + mountMode = "mount-mode" + mountMSize = "mount-msize" + mountOptions = "mount-options" + mountPortFlag = "mount-port" + mountTypeFlag = "mount-type" + mountUID = "mount-uid" disableDriverMounts = "disable-driver-mounts" cacheImages = "cache-images" uuid = "uuid" @@ -153,6 +162,15 @@ func initMinikubeFlags() { startCmd.Flags().String(containerRuntime, constants.DefaultContainerRuntime, fmt.Sprintf("The container runtime to be used (%s).", strings.Join(cruntime.ValidRuntimes(), ", "))) startCmd.Flags().Bool(createMount, false, "This will start the mount daemon and automatically mount files into minikube.") startCmd.Flags().String(mountString, constants.DefaultMountDir+":/minikube-host", "The argument to pass the minikube mount command on start.") + startCmd.Flags().String(mount9PVersion, defaultMount9PVersion, mount9PVersionDescription) + startCmd.Flags().String(mountGID, defaultMountGID, mountGIDDescription) + startCmd.Flags().String(mountIPFlag, defaultMountIP, mountIPDescription) + startCmd.Flags().Uint(mountMode, defaultMountMode, mountModeDescription) + startCmd.Flags().Int(mountMSize, defaultMountMSize, mountMSizeDescription) + startCmd.Flags().StringSlice(mountOptions, defaultMountOptions(), mountOptionsDescription) + startCmd.Flags().Uint16(mountPortFlag, defaultMountPort, mountPortDescription) + startCmd.Flags().String(mountTypeFlag, defaultMountType, mountTypeDescription) + startCmd.Flags().String(mountUID, defaultMountUID, mountUIDDescription) startCmd.Flags().StringSlice(config.AddonListFlag, nil, "Enable addons. see `minikube addons list` for a list of valid addon names.") startCmd.Flags().String(criSocket, "", "The cri socket path to be used.") startCmd.Flags().String(networkPlugin, "", "Kubelet network plug-in to use (default: auto)") @@ -466,6 +484,15 @@ func generateNewConfigFromFlags(cmd *cobra.Command, k8sVersion string, drvName s CertExpiration: viper.GetDuration(certExpiration), Mount: viper.GetBool(createMount), MountString: viper.GetString(mountString), + Mount9PVersion: viper.GetString(mount9PVersion), + MountGID: viper.GetString(mountGID), + MountIP: viper.GetString(mountIPFlag), + MountMode: viper.GetUint(mountMode), + MountMSize: viper.GetInt(mountMSize), + MountOptions: viper.GetStringSlice(mountOptions), + MountPort: uint16(viper.GetUint(mountPortFlag)), + MountType: viper.GetString(mountTypeFlag), + MountUID: viper.GetString(mountUID), KubernetesConfig: config.KubernetesConfig{ KubernetesVersion: k8sVersion, ClusterName: ClusterFlagValue(), @@ -675,6 +702,15 @@ func updateExistingConfigFromFlags(cmd *cobra.Command, existing *config.ClusterC updateDurationFromFlag(cmd, &cc.CertExpiration, certExpiration) updateBoolFromFlag(cmd, &cc.Mount, createMount) updateStringFromFlag(cmd, &cc.MountString, mountString) + updateStringFromFlag(cmd, &cc.Mount9PVersion, mount9PVersion) + updateStringFromFlag(cmd, &cc.MountGID, mountGID) + updateStringFromFlag(cmd, &cc.MountIP, mountIPFlag) + updateUintFromFlag(cmd, &cc.MountMode, mountMode) + updateIntFromFlag(cmd, &cc.MountMSize, mountMSize) + updateStringSliceFromFlag(cmd, &cc.MountOptions, mountOptions) + updateUint16FromFlag(cmd, &cc.MountPort, mountPortFlag) + updateStringFromFlag(cmd, &cc.MountType, mountTypeFlag) + updateStringFromFlag(cmd, &cc.MountUID, mountUID) if cmd.Flags().Changed(kubernetesVersion) { cc.KubernetesConfig.KubernetesVersion = getKubernetesVersion(existing) @@ -746,6 +782,20 @@ func updateDurationFromFlag(cmd *cobra.Command, v *time.Duration, key string) { } } +// updateUintFromFlag will update the existing uint from the flag. +func updateUintFromFlag(cmd *cobra.Command, v *uint, key string) { + if cmd.Flags().Changed(key) { + *v = viper.GetUint(key) + } +} + +// updateUint16FromFlag will update the existing uint16 from the flag. +func updateUint16FromFlag(cmd *cobra.Command, v *uint16, key string) { + if cmd.Flags().Changed(key) { + *v = uint16(viper.GetUint(key)) + } +} + // interpretWaitFlag interprets the wait flag and respects the legacy minikube users // returns map of components to wait for func interpretWaitFlag(cmd cobra.Command) map[string]bool { diff --git a/pkg/minikube/config/types.go b/pkg/minikube/config/types.go index 0e6b7a3f3d6d..c9606f41d368 100644 --- a/pkg/minikube/config/types.go +++ b/pkg/minikube/config/types.go @@ -87,6 +87,15 @@ type ClusterConfig struct { CertExpiration time.Duration Mount bool MountString string + Mount9PVersion string + MountGID string + MountIP string + MountMode uint + MountMSize int + MountOptions []string + MountPort uint16 + MountType string + MountUID string } // KubernetesConfig contains the parameters used to configure the VM Kubernetes. diff --git a/pkg/minikube/constants/constants.go b/pkg/minikube/constants/constants.go index 9f1f093da09e..5bd887d09a3b 100644 --- a/pkg/minikube/constants/constants.go +++ b/pkg/minikube/constants/constants.go @@ -124,6 +124,25 @@ const ( // DefaultCertExpiration is the amount of time in the future a certificate will expire in by default, which is 3 years DefaultCertExpiration = time.Hour * 24 * 365 * 3 + + // Mount9PVersionFlag is the flag used to set the mount 9P version + Mount9PVersionFlag = "9p-version" + // MountGIDFlag is the flag used to set the mount GID + MountGIDFlag = "gid" + // MountIPFlag is the flag used to set the mount IP + MountIPFlag = "ip" + // MountMSizeFlag is the flag used to set the mount msize + MountMSizeFlag = "msize" + // MountModeFlag is the flag used to set the mount mode + MountModeFlag = "mode" + // MountOptionsFlag is the flag used to set the mount options + MountOptionsFlag = "options" + // MountPortFlag is the flag used to set the mount port + MountPortFlag = "port" + // MountTypeFlag is the flag used to set the mount type + MountTypeFlag = "type" + // MountUIDFlag is the flag used to set the mount UID + MountUIDFlag = "uid" ) var ( diff --git a/pkg/minikube/node/config.go b/pkg/minikube/node/config.go index aee864dadd47..35f464630ee7 100644 --- a/pkg/minikube/node/config.go +++ b/pkg/minikube/node/config.go @@ -51,22 +51,20 @@ func showVersionInfo(k8sVersion string, cr cruntime.Manager) { } // configureMounts configures any requested filesystem mounts -func configureMounts(wg *sync.WaitGroup, mount bool, mountString string) { +func configureMounts(wg *sync.WaitGroup, cc config.ClusterConfig) { wg.Add(1) defer wg.Done() - if !mount { + if !cc.Mount { return } - out.Step(style.Mounting, "Creating mount {{.name}} ...", out.V{"name": mountString}) + out.Step(style.Mounting, "Creating mount {{.name}} ...", out.V{"name": cc.MountString}) path := os.Args[0] - mountDebugVal := 0 - if klog.V(8).Enabled() { - mountDebugVal = 1 - } profile := viper.GetString("profile") - mountCmd := exec.Command(path, "mount", "-p", profile, fmt.Sprintf("--v=%d", mountDebugVal), mountString) + + args := generateMountArgs(profile, cc) + mountCmd := exec.Command(path, args...) mountCmd.Env = append(os.Environ(), constants.IsMinikubeChildProcess+"=true") if klog.V(8).Enabled() { mountCmd.Stdout = os.Stdout @@ -79,3 +77,34 @@ func configureMounts(wg *sync.WaitGroup, mount bool, mountString string) { exit.Error(reason.HostMountPid, "Error writing mount pid", err) } } + +func generateMountArgs(profile string, cc config.ClusterConfig) []string { + mountDebugVal := 0 + if klog.V(8).Enabled() { + mountDebugVal = 1 + } + + args := []string{"mount", cc.MountString} + flags := []struct { + name string + value string + }{ + {"profile", profile}, + {"v", fmt.Sprintf("%d", mountDebugVal)}, + {constants.Mount9PVersionFlag, cc.Mount9PVersion}, + {constants.MountGIDFlag, cc.MountGID}, + {constants.MountIPFlag, cc.MountIP}, + {constants.MountMSizeFlag, fmt.Sprintf("%d", cc.MountMSize)}, + {constants.MountModeFlag, fmt.Sprintf("%d", cc.MountMode)}, + {constants.MountPortFlag, fmt.Sprintf("%d", cc.MountPort)}, + {constants.MountTypeFlag, cc.MountType}, + {constants.MountUIDFlag, cc.MountUID}, + } + for _, flag := range flags { + args = append(args, fmt.Sprintf("--%s", flag.name), flag.value) + } + for _, option := range cc.MountOptions { + args = append(args, fmt.Sprintf("--%s", constants.MountOptionsFlag), option) + } + return args +} diff --git a/pkg/minikube/node/start.go b/pkg/minikube/node/start.go index 5f9fe232f802..98fdb13094e5 100644 --- a/pkg/minikube/node/start.go +++ b/pkg/minikube/node/start.go @@ -148,7 +148,7 @@ func Start(starter Starter, apiServer bool) (*kubeconfig.Settings, error) { var wg sync.WaitGroup if !driver.IsKIC(starter.Cfg.Driver) { - go configureMounts(&wg, starter.Cfg.Mount, starter.Cfg.MountString) + go configureMounts(&wg, *starter.Cfg) } wg.Add(1) diff --git a/test/integration/mount_start_test.go b/test/integration/mount_start_test.go index 5a23796d366a..76162dfe19a4 100644 --- a/test/integration/mount_start_test.go +++ b/test/integration/mount_start_test.go @@ -21,10 +21,21 @@ package integration import ( "context" + "fmt" "os/exec" + "runtime" + "strings" "testing" ) +const ( + mountGID = "0" + mountMSize = "6543" + mountMode = "0777" + mountPort = "46464" + mountUID = "0" +) + // TestMountStart tests using the mount command on start func TestMountStart(t *testing.T) { if NoneDriver() { @@ -72,7 +83,7 @@ func TestMountStart(t *testing.T) { func validateStartWithMount(ctx context.Context, t *testing.T, profile string) { defer PostMortemLogs(t, profile) - args := []string{"start", "-p", profile, "--memory=2048", "--mount"} + args := []string{"start", "-p", profile, "--memory=2048", "--mount", "--mount-gid", mountGID, "--mount-msize", mountMSize, "--mount-mode", mountMode, "--mount-port", mountPort, "--mount-uid", mountUID} args = append(args, StartArgs()...) rr, err := Run(t, exec.CommandContext(ctx, Target(), args...)) if err != nil { @@ -84,11 +95,63 @@ func validateStartWithMount(ctx context.Context, t *testing.T, profile string) { func validateMount(ctx context.Context, t *testing.T, profile string) { defer PostMortemLogs(t, profile) - args := []string{"-p", profile, "ssh", "ls", "/minikube-host"} + sshArgs := []string{"-p", profile, "ssh", "--"} + + args := sshArgs + args = append(args, "ls", "/minikube-host") rr, err := Run(t, exec.CommandContext(ctx, Target(), args...)) if err != nil { t.Fatalf("mount failed: %q : %v", rr.Command(), err) } + + // Docker has it's own mounting method, it doesn't respect the mounting flags + if DockerDriver() { + return + } + + // skipping macOS due to https://github.com/kubernetes/minikube/issues/13070 + if runtime.GOOS != "darwin" { + args = sshArgs + args = append(args, "stat", "--format", "'%a'", "/minikube-host") + rr, err = Run(t, exec.CommandContext(ctx, Target(), args...)) + if err != nil { + t.Fatalf("failed to get directory mode: %v", err) + } + + const want = "777" + if !strings.Contains(rr.Output(), want) { + t.Errorf("wanted mode to be %q; got: %q", want, rr.Output()) + } + } + + // We can't get the mount details with Hyper-V + if HyperVDriver() { + return + } + + args = sshArgs + args = append(args, "mount", "|", "grep", "9p") + rr, err = Run(t, exec.CommandContext(ctx, Target(), args...)) + if err != nil { + t.Fatalf("failed to get mount information: %v", err) + } + + flags := []struct { + key string + expected string + }{ + {"gid", mountGID}, + {"msize", mountMSize}, + {"port", mountPort}, + {"uid", mountUID}, + } + + for _, flag := range flags { + want := fmt.Sprintf("%s=%s", flag.key, flag.expected) + if !strings.Contains(rr.Output(), want) { + t.Errorf("wanted %s to be: %q; got: %q", flag.key, want, rr.Output()) + } + } } // validateMountStop stops a cluster