diff --git a/cmd/minikube/cmd/root.go b/cmd/minikube/cmd/root.go index f028873b91e8..336836a50a12 100644 --- a/cmd/minikube/cmd/root.go +++ b/cmd/minikube/cmd/root.go @@ -21,6 +21,7 @@ import ( "fmt" "io/ioutil" "os" + "runtime" "strings" "github.com/docker/machine/libmachine" @@ -108,6 +109,12 @@ func Execute() { flag.Usage = translate.T(flag.Usage) }) + if runtime.GOOS == "linux" { + // add minikube binaries to the path + targetDir := constants.MakeMiniPath("bin") + addToPath(targetDir) + } + if err := RootCmd.Execute(); err != nil { // Cobra already outputs the error, typically because the user provided an unknown command. os.Exit(exit.BadUsage) @@ -281,3 +288,8 @@ func getClusterBootstrapper(api libmachine.API, bootstrapperName string) (bootst return b, nil } + +func addToPath(dir string) { + path := os.Getenv("PATH") + os.Setenv("PATH", fmt.Sprintf("%s:%s", dir, path)) +} diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index ed4ab3de1049..71552e94e41c 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -24,7 +24,6 @@ import ( "os/exec" "os/user" "path/filepath" - "regexp" "runtime" "strconv" "strings" @@ -44,6 +43,7 @@ import ( "github.com/spf13/viper" "golang.org/x/sync/errgroup" cmdcfg "k8s.io/minikube/cmd/minikube/cmd/config" + "k8s.io/minikube/pkg/drivers" "k8s.io/minikube/pkg/minikube/bootstrapper" "k8s.io/minikube/pkg/minikube/bootstrapper/kubeadm" "k8s.io/minikube/pkg/minikube/cluster" @@ -274,7 +274,8 @@ func runStart(cmd *cobra.Command, args []string) { registryMirror = viper.GetStringSlice("registry_mirror") } - if err := cmdcfg.IsValidDriver(runtime.GOOS, viper.GetString(vmDriver)); err != nil { + driver := viper.GetString(vmDriver) + if err := cmdcfg.IsValidDriver(runtime.GOOS, driver); err != nil { exit.WithCodeT( exit.Failure, "The driver '{{.driver}}' is not supported on {{.os}}", @@ -284,7 +285,7 @@ func runStart(cmd *cobra.Command, args []string) { validateConfig() validateUser() - validateDriverVersion(viper.GetString(vmDriver)) + validateDriverVersion(driver) k8sVersion, isUpgrade := getKubernetesVersion() config, err := generateCfgFromFlags(cmd, k8sVersion) @@ -981,9 +982,21 @@ func validateDriverVersion(vmDriver string) { var driverExecutable string driverDocumentation := fmt.Sprintf("%s%s#driver-installation", constants.DriverDocumentation, vmDriver) + minikubeVersion, err := version.GetSemverVersion() + if err != nil { + out.WarningT("Error parsing minukube version: {{.error}}", out.V{"error": err}) + return + } + switch vmDriver { case constants.DriverKvm2: driverExecutable = fmt.Sprintf("docker-machine-driver-%s", constants.DriverKvm2) + targetDir := constants.MakeMiniPath("bin") + err := drivers.Download(driverExecutable, targetDir, minikubeVersion) + if err != nil { + out.WarningT("Error downloading driver: {{.error}}", out.V{"error": err}) + } + return case constants.DriverHyperkit: driverExecutable = fmt.Sprintf("docker-machine-driver-%s", constants.DriverHyperkit) default: // driver doesn't support version @@ -1000,7 +1013,7 @@ func validateDriverVersion(vmDriver string) { return } - v := extractVMDriverVersion(string(output)) + v := drivers.ExtractVMDriverVersion(string(output)) // if the driver doesn't have return any version, it is really old, we force a upgrade. if len(v) == 0 && !viper.GetBool(force) { @@ -1017,12 +1030,6 @@ func validateDriverVersion(vmDriver string) { return } - minikubeVersion, err := version.GetSemverVersion() - if err != nil { - out.WarningT("Error parsing minikube version: {{.error}}", out.V{"error": err}) - return - } - if vmDriverVersion.LT(minikubeVersion) { out.WarningT( "The installed version of '{{.driver_executable}}' ({{.driver_version}}) is no longer current. Upgrade: {{.documentation_url}}", @@ -1030,20 +1037,3 @@ func validateDriverVersion(vmDriver string) { ) } } - -// 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 { - versionRegex := regexp.MustCompile(`version:(.*)`) - matches := versionRegex.FindStringSubmatch(s) - - if len(matches) != 2 { - return "" - } - - v := strings.TrimSpace(matches[1]) - return strings.TrimPrefix(v, version.VersionPrefix) -} diff --git a/cmd/minikube/cmd/start_test.go b/cmd/minikube/cmd/start_test.go deleted file mode 100644 index 446091c17f0e..000000000000 --- a/cmd/minikube/cmd/start_test.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2019 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 cmd - -import ( - "testing" -) - -func Test_extractVMDriverVersion(t *testing.T) { - v := extractVMDriverVersion("") - if len(v) != 0 { - t.Error("Expected empty string") - } - - v = extractVMDriverVersion("random text") - if len(v) != 0 { - t.Error("Expected empty string") - } - - expectedVersion := "1.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") - if expectedVersion != v { - t.Errorf("Expected version: %s, got: %s", expectedVersion, v) - } -} diff --git a/pkg/drivers/drivers.go b/pkg/drivers/drivers.go index ca5262faabc3..60c24721d304 100644 --- a/pkg/drivers/drivers.go +++ b/pkg/drivers/drivers.go @@ -20,15 +20,29 @@ import ( "io" "io/ioutil" "os" + "os/exec" + "path" "path/filepath" + "regexp" + "strings" "syscall" + "github.com/blang/semver" "github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/mcnflag" "github.com/docker/machine/libmachine/mcnutils" "github.com/docker/machine/libmachine/ssh" "github.com/golang/glog" + "github.com/hashicorp/go-getter" "github.com/pkg/errors" + "k8s.io/minikube/pkg/version" + + "k8s.io/minikube/pkg/minikube/out" + "k8s.io/minikube/pkg/util" +) + +const ( + driverKVMDownloadURL = "https://storage.googleapis.com/minikube/releases/latest/docker-machine-driver-kvm2" ) // GetDiskPath returns the path of the machine disk image @@ -136,3 +150,88 @@ func fixPermissions(path string) error { } return nil } + +// Download downloads driver if it is not present, or if the version is old +func Download(driver, destination string, minikubeVersion semver.Version) error { + _, err := exec.LookPath(driver) + // if file driver doesn't exist, download it + if err != nil { + return download(driver, destination) + } + + cmd := exec.Command(driver, "version") + output, err := cmd.Output() + // if driver doesnt support 'version', it is old, download it + if err != nil { + return download(driver, destination) + } + + v := ExtractVMDriverVersion(string(output)) + + // if the driver doesn't return any version, download it + if len(v) == 0 { + return download(driver, destination) + } + + vmDriverVersion, err := semver.Make(v) + if err != nil { + return errors.Wrap(err, "can't parse driver version") + } + + // if the current driver version is older, download newer + if vmDriverVersion.LT(minikubeVersion) { + return download(driver, destination) + } + + return nil +} + +func download(driver, destination string) error { + // only support kvm2 for now + if driver != "docker-machine-driver-kvm2" { + return nil + } + + out.T(out.Happy, "Downloading driver {{.driver}}:", out.V{"driver": driver}) + + targetFilepath := path.Join(destination, "docker-machine-driver-kvm2") + os.Remove(targetFilepath) + + url := driverKVMDownloadURL + + opts := []getter.ClientOption{getter.WithProgress(util.DefaultProgressBar)} + client := &getter.Client{ + Src: url, + Dst: targetFilepath, + Mode: getter.ClientModeFile, + Options: opts, + } + + if err := client.Get(); err != nil { + return errors.Wrapf(err, "can't download driver %s from: %s", driver, url) + } + + err := os.Chmod(targetFilepath, 0777) + if err != nil { + return errors.Wrap(err, "chmod error") + } + + return nil +} + +// 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 { + versionRegex := regexp.MustCompile(`version:(.*)`) + matches := versionRegex.FindStringSubmatch(s) + + if len(matches) != 2 { + return "" + } + + v := strings.TrimSpace(matches[1]) + return strings.TrimPrefix(v, version.VersionPrefix) +} diff --git a/pkg/drivers/drivers_test.go b/pkg/drivers/drivers_test.go index 98982b9b36ea..c4d9aafe03b0 100644 --- a/pkg/drivers/drivers_test.go +++ b/pkg/drivers/drivers_test.go @@ -48,3 +48,27 @@ func Test_createDiskImage(t *testing.T) { t.Errorf("Disk size is %v, want %v", fi.Size(), sizeInBytes) } } + +func TestExtractVMDriverVersion(t *testing.T) { + v := ExtractVMDriverVersion("") + if len(v) != 0 { + t.Error("Expected empty string") + } + + v = ExtractVMDriverVersion("random text") + if len(v) != 0 { + t.Error("Expected empty string") + } + + expectedVersion := "1.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") + if expectedVersion != v { + t.Errorf("Expected version: %s, got: %s", expectedVersion, v) + } +} diff --git a/pkg/util/downloader.go b/pkg/util/downloader.go index 81d951276a2d..f6ed32a58a30 100644 --- a/pkg/util/downloader.go +++ b/pkg/util/downloader.go @@ -69,7 +69,7 @@ func (f DefaultDownloader) CacheMinikubeISOFromURL(url string) error { // Predictable temp destination so that resume can function tmpDst := dst + ".download" - opts := []getter.ClientOption{getter.WithProgress(defaultProgressBar)} + opts := []getter.ClientOption{getter.WithProgress(DefaultProgressBar)} client := &getter.Client{ Src: urlWithChecksum, Dst: tmpDst, diff --git a/pkg/util/progressbar.go b/pkg/util/progressbar.go index 14bb33f169bc..f144d4266ada 100644 --- a/pkg/util/progressbar.go +++ b/pkg/util/progressbar.go @@ -15,9 +15,9 @@ limitations under the License. */ // This file implements a go-getter wrapper for cheggaaa progress bar - // based on: // https://github.com/hashicorp/go-getter/blob/master/cmd/go-getter/progress_tracking.go + package util import ( @@ -29,7 +29,7 @@ import ( "github.com/hashicorp/go-getter" ) -var defaultProgressBar getter.ProgressTracker = &progressBar{} +var DefaultProgressBar getter.ProgressTracker = &progressBar{} type progressBar struct { lock sync.Mutex