From c0874e2a7eaf44af672e089faa822b672124a135 Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Wed, 18 Sep 2019 13:18:15 -0700 Subject: [PATCH] Add --interactive flag support --- cmd/minikube/cmd/start.go | 31 ++----- pkg/drivers/drivers.go | 170 +++++++++++++++++++++--------------- pkg/drivers/drivers_test.go | 8 +- pkg/minikube/out/style.go | 2 +- 4 files changed, 110 insertions(+), 101 deletions(-) diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index f8c4643669..a868cdfb12 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -105,6 +105,7 @@ const ( hostDNSResolver = "host-dns-resolver" waitUntilHealthy = "wait" force = "force" + interactive = "interactive" waitTimeout = "wait-timeout" nativeSSH = "native-ssh" ) @@ -140,6 +141,7 @@ func initMinikubeFlags() { viper.AutomaticEnv() startCmd.Flags().Bool(force, false, "Force minikube to perform possibly dangerous operations") + startCmd.Flags().Bool(interactive, true, "Allow user prompts for more information") startCmd.Flags().Int(cpus, constants.DefaultCPUS, "Number of CPUs allocated to the minikube VM.") startCmd.Flags().String(memory, constants.DefaultMemorySize, "Amount of RAM allocated to the minikube VM (format: [], where unit = b, k, m or g).") @@ -290,7 +292,10 @@ func runStart(cmd *cobra.Command, args []string) { validateFlags(driver) validateUser(driver) - installOrUpdateDriver(driver) + if err := drivers.InstallOrUpdate(driver, viper.GetBool(interactive)); err != nil { + glog.Errorf("error: %v", err) + out.WarningT("Unable to update {{.driver}} driver: {{.error}}", out.V{"driver": driver, "error": err}) + } k8sVersion, isUpgrade := getKubernetesVersion(oldConfig) config, err := generateCfgFromFlags(cmd, k8sVersion, driver) @@ -1055,27 +1060,3 @@ func configureMounts() { func saveConfig(clusterCfg *cfg.Config) error { return cfg.CreateProfile(viper.GetString(cfg.MachineProfile), clusterCfg) } - -func installOrUpdateDriver(driver string) { - var driverExecutable string - switch driver { - case constants.DriverKvm2: - driverExecutable = fmt.Sprintf("docker-machine-driver-%s", constants.DriverKvm2) - case constants.DriverHyperkit: - driverExecutable = fmt.Sprintf("docker-machine-driver-%s", constants.DriverHyperkit) - default: // driver doesn't install or update - return - } - - minikubeVersion, err := version.GetSemverVersion() - if err != nil { - out.WarningT("Error parsing minikube version: {{.error}}", out.V{"error": err}) - return - } - - targetDir := constants.MakeMiniPath("bin") - err = drivers.InstallOrUpdate(driverExecutable, targetDir, minikubeVersion) - if err != nil { - out.WarningT("Error downloading driver: {{.error}}", out.V{"error": err}) - } -} diff --git a/pkg/drivers/drivers.go b/pkg/drivers/drivers.go index 52913dc925..9a227ede10 100644 --- a/pkg/drivers/drivers.go +++ b/pkg/drivers/drivers.go @@ -22,7 +22,6 @@ import ( "io/ioutil" "os" "os/exec" - "path" "path/filepath" "regexp" "strings" @@ -38,6 +37,7 @@ import ( "github.com/pkg/errors" "k8s.io/minikube/pkg/version" + "k8s.io/minikube/pkg/minikube/constants" "k8s.io/minikube/pkg/minikube/out" "k8s.io/minikube/pkg/util" ) @@ -123,14 +123,14 @@ func MakeDiskImage(d *drivers.BaseDriver, boot2dockerURL string, diskSize int) e return errors.Wrapf(err, "createRawDiskImage(%s)", diskPath) } machPath := d.ResolveStorePath(".") - if err := fixPermissions(machPath); err != nil { + if err := fixMachinePermissions(machPath); err != nil { return errors.Wrapf(err, "fixing permissions on %s", machPath) } } return nil } -func fixPermissions(path string) error { +func fixMachinePermissions(path string) error { glog.Infof("Fixing permissions on %s ...", path) if err := os.Chown(path, syscall.Getuid(), syscall.Getegid()); err != nil { return errors.Wrap(err, "chown dir") @@ -149,44 +149,110 @@ func fixPermissions(path string) error { } // InstallOrUpdate downloads driver if it is not present, or updates it if there's a newer version -func InstallOrUpdate(driver, destination string, v semver.Version) error { - glog.Infof("InstallOrUpdate(%s): dest=%s, version=%s, PATH=%s", driver, destination, v, os.Getenv("PATH")) - - _, err := exec.LookPath(driver) - // if file driver doesn't exist, download it - if err != nil { - glog.Infof("LookPath %s: %v", driver, err) - return download(driver, v, destination) +func InstallOrUpdate(driver string, interactive bool) error { + if driver != constants.DriverKvm2 && driver != constants.DriverHyperkit { + return nil } - cmd := exec.Command(driver, "version") - output, err := cmd.Output() - // if driver doesnt support 'version', it is old, download it + v, err := version.GetSemverVersion() if err != nil { - glog.Infof("%s version: %v", driver, err) - return download(driver, v, destination) + out.WarningT("Error parsing minikube version: {{.error}}", out.V{"error": err}) + return err } - ev := ExtractVMDriverVersion(string(output)) + executable := fmt.Sprintf("docker-machine-driver-%s", driver) + path, err := validateDriver(executable, v) + if err != nil { + glog.Warningf("%s: %v", driver, executable) + path = filepath.Join(constants.MakeMiniPath("bin"), executable) + derr := download(executable, path, v) + if derr != nil { + return derr + } + } + return fixDriverPermissions(driver, path, interactive) +} - // if the driver doesn't return any version, download it +// fixDriverPermissions fixes the permissions on a driver +func fixDriverPermissions(driver string, path string, interactive bool) error { + // Everything is easy but hyperkit + if driver != constants.DriverHyperkit { + return os.Chmod(path, 0755) + } + + // Hyperkit land + info, err := os.Stat(path) + if err != nil { + return err + } + + cmds := []*exec.Cmd{} + owner := info.Sys().(*syscall.Stat_t).Uid + m := info.Mode() + glog.Infof("%s owner: %d - permissions: %s", path, owner, m) + if owner != 0 { + cmds = append(cmds, exec.Command("sudo", "chown", "root:wheel", path)) + } + + if m&os.ModeSetuid == 0 { + cmds = append(cmds, exec.Command("sudo", "chmod", "u+s", path)) + } + + // No work to be done! + if len(cmds) == 0 { + return nil + } + + var example strings.Builder + for _, c := range cmds { + example.WriteString(fmt.Sprintf(" $ %s \n", strings.Join(c.Args, " "))) + } + + out.T(out.Permissions, "The '{{.driver}}' driver requires elevated permissions. The following commands will be executed:\n\n{{ .example }}\n", out.V{"driver": driver, "example": example.String()}) + for _, c := range cmds { + testArgs := append([]string{"-n"}, c.Args[1:]...) + test := exec.Command("sudo", testArgs...) + glog.Infof("testing: %v", test.Args) + if err := test.Run(); err != nil { + glog.Infof("%v may require a password: %v", c.Args, err) + if !interactive { + return fmt.Errorf("%v requires a password, and --interactive=false", c.Args) + } + } + glog.Infof("running: %v", c.Args) + err := c.Run() + if err != nil { + return errors.Wrapf(err, "%v", c.Args) + } + } + return nil +} + +// validateDriver validates if a driver appears to be up-to-date and installed properly +func validateDriver(driver string, v semver.Version) (string, error) { + path, err := exec.LookPath(driver) + if err != nil { + return path, err + } + + output, err := exec.Command(path, "version").Output() + if err != nil { + return path, err + } + + ev := extractVMDriverVersion(string(output)) if len(ev) == 0 { - glog.Infof("%s: unable to extract version from %q", driver, output) - return download(driver, v, destination) + return path, fmt.Errorf("%s: unable to extract version from %q", driver, output) } vmDriverVersion, err := semver.Make(ev) if err != nil { - return errors.Wrap(err, "can't parse driver version") + return path, errors.Wrap(err, "can't parse driver version") } - - // if the current driver version is older, download newer if vmDriverVersion.LT(v) { - glog.Infof("%s is version %s, want %s", driver, vmDriverVersion, v) - return download(driver, v, destination) + return path, fmt.Errorf("%s is version %s, want %s", driver, vmDriverVersion, v) } - - return nil + return path, nil } func driverWithChecksumURL(driver string, v semver.Version) string { @@ -194,50 +260,31 @@ func driverWithChecksumURL(driver string, v semver.Version) string { return fmt.Sprintf("%s?checksum=file:%s.sha256", base, base) } -func download(driver string, v semver.Version, destination string) error { - // supports kvm2 and hyperkit - if driver != "docker-machine-driver-kvm2" && driver != "docker-machine-driver-hyperkit" { - return nil - } - +// download an arbitrary driver +func download(driver string, destination string, v semver.Version) error { out.T(out.FileDownload, "Downloading driver {{.driver}}:", out.V{"driver": driver}) - targetFilepath := path.Join(destination, driver) - os.Remove(targetFilepath) + os.Remove(destination) url := driverWithChecksumURL(driver, v) client := &getter.Client{ Src: url, - Dst: targetFilepath, + Dst: destination, Mode: getter.ClientModeFile, Options: []getter.ClientOption{getter.WithProgress(util.DefaultProgressBar)}, } glog.Infof("Downloading: %+v", client) - if err := client.Get(); err != nil { return errors.Wrapf(err, "download failed: %s", url) } - - err := os.Chmod(targetFilepath, 0755) - if err != nil { - return errors.Wrap(err, "chmod error") - } - - if driver == "docker-machine-driver-hyperkit" { - err := setHyperKitPermissions(targetFilepath) - if err != nil { - return errors.Wrap(err, "setting hyperkit permission") - } - } - return nil } -// ExtractVMDriverVersion extracts the driver version. +// extractVMDriverVersion extracts the driver version. // KVM and Hyperkit drivers support the 'version' command, that display the information as: // version: vX.X.X // commit: XXXX // This method returns the version 'vX.X.X' or empty if the version isn't found. -func ExtractVMDriverVersion(s string) string { +func extractVMDriverVersion(s string) string { versionRegex := regexp.MustCompile(`version:(.*)`) matches := versionRegex.FindStringSubmatch(s) @@ -248,22 +295,3 @@ func ExtractVMDriverVersion(s string) string { v := strings.TrimSpace(matches[1]) return strings.TrimPrefix(v, version.VersionPrefix) } - -func setHyperKitPermissions(driverPath string) error { - msg := fmt.Sprintf("A new hyperkit driver was installed. It needs elevated permissions to run. The following commands will be executed:\n\n $ sudo chown root:wheel %s\n $ sudo chmod u+s %s\n", driverPath, driverPath) - out.T(out.Permissions, msg, out.V{}) - - cmd := exec.Command("sudo", "chown", "root:wheel", driverPath) - err := cmd.Run() - if err != nil { - return errors.Wrap(err, "chown root:wheel") - } - - cmd = exec.Command("sudo", "chmod", "u+s", driverPath) - err = cmd.Run() - if err != nil { - return errors.Wrap(err, "chmod u+s") - } - - return nil -} diff --git a/pkg/drivers/drivers_test.go b/pkg/drivers/drivers_test.go index c4d9aafe03..10e42e3e44 100644 --- a/pkg/drivers/drivers_test.go +++ b/pkg/drivers/drivers_test.go @@ -50,24 +50,24 @@ func Test_createDiskImage(t *testing.T) { } func TestExtractVMDriverVersion(t *testing.T) { - v := ExtractVMDriverVersion("") + v := extractVMDriverVersion("") if len(v) != 0 { t.Error("Expected empty string") } - v = ExtractVMDriverVersion("random text") + v = extractVMDriverVersion("random text") if len(v) != 0 { t.Error("Expected empty string") } expectedVersion := "1.2.3" - v = ExtractVMDriverVersion("version: v1.2.3") + v = extractVMDriverVersion("version: v1.2.3") if expectedVersion != v { t.Errorf("Expected version: %s, got: %s", expectedVersion, v) } - v = ExtractVMDriverVersion("version: 1.2.3") + v = extractVMDriverVersion("version: 1.2.3") if expectedVersion != v { t.Errorf("Expected version: %s, got: %s", expectedVersion, v) } diff --git a/pkg/minikube/out/style.go b/pkg/minikube/out/style.go index 44ae64efe8..4791787413 100644 --- a/pkg/minikube/out/style.go +++ b/pkg/minikube/out/style.go @@ -78,7 +78,7 @@ var styles = map[StyleEnum]style{ Documentation: {Prefix: "📘 "}, Issues: {Prefix: "⁉️ "}, Issue: {Prefix: " ▪ ", LowPrefix: lowIndent}, // Indented bullet - Check: {Prefix: "✔️ "}, + Check: {Prefix: "✅ "}, Celebration: {Prefix: "🎉 "}, Workaround: {Prefix: "👉 ", LowPrefix: lowIndent},