diff --git a/cmd/minikube/cmd/cache.go b/cmd/minikube/cmd/cache.go index b6fdb246cab2..488fd241d9e0 100644 --- a/cmd/minikube/cmd/cache.go +++ b/cmd/minikube/cmd/cache.go @@ -67,7 +67,7 @@ var deleteCacheCmd = &cobra.Command{ } // LoadCachedImagesInConfigFile loads the images currently in the config file (minikube start) -func LoadCachedImagesInConfigFile() error { +func LoadCachedImagesInConfigFile(load bool) error { configFile, err := config.ReadConfig() if err != nil { return err @@ -77,7 +77,11 @@ func LoadCachedImagesInConfigFile() error { for key := range values.(map[string]interface{}) { images = append(images, key) } - return machine.CacheAndLoadImages(images) + if load { + return machine.CacheAndLoadImages(images) + } else { + return machine.CacheImages(images, constants.ImageCacheDir) + } } return nil } diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index f1ce4eacb63a..e956d5311d30 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -85,6 +85,7 @@ const ( vsockPorts = "hyperkit-vsock-ports" gpu = "gpu" embedCerts = "embed-certs" + downloadOnly = "download-only" ) var ( @@ -131,6 +132,7 @@ func init() { startCmd.Flags().String(featureGates, "", "A set of key=value pairs that describe feature gates for alpha/experimental features.") // TODO(tstromberg): Flip cacheImages to true once it can be stabilized startCmd.Flags().Bool(cacheImages, false, "If true, cache docker images for the current bootstrapper and load them into the machine.") + startCmd.Flags().Bool(downloadOnly, false, "If true, only download and cache files for later use - don't install or start anything.") startCmd.Flags().Var(&extraOptions, "extra-config", `A set of key=value pairs that describe configuration that may be passed to different components. The key should be '.' separated, and the first part before the dot is the component to apply the configuration to. @@ -180,6 +182,22 @@ func runStart(cmd *cobra.Command, args []string) { if err != nil { exit.WithError("Failed to get machine client", err) } + + if viper.GetBool(downloadOnly) { + if err := cluster.CacheISO(m, config.MachineConfig); err != nil { + exit.WithError("Failed to cache ISO", err) + } + if err := bootstrapper.CacheBinaries(nil, config.KubernetesConfig); err != nil { + exit.WithError("Failed to cache binaries", err) + } + waitCacheImages(&cacheGroup) + if err := LoadCachedImagesInConfigFile(false); err != nil { + exit.WithError("Failed to cache images", err) + } + console.OutStyle("check", "Download complete and in download only mode") + return + } + host, preexisting := startHost(m, config.MachineConfig) ip := validateNetwork(host) @@ -202,7 +220,7 @@ func runStart(cmd *cobra.Command, args []string) { bootstrapCluster(bs, cr, runner, config.KubernetesConfig, preexisting) validateCluster(bs, cr, runner, ip) configureMounts() - if err = LoadCachedImagesInConfigFile(); err != nil { + if err = LoadCachedImagesInConfigFile(true); err != nil { console.Failure("Unable to load cached images from config file.") } diff --git a/pkg/minikube/bootstrapper/bootstrapper.go b/pkg/minikube/bootstrapper/bootstrapper.go index 4140f58548a9..667a79cb8795 100644 --- a/pkg/minikube/bootstrapper/bootstrapper.go +++ b/pkg/minikube/bootstrapper/bootstrapper.go @@ -17,9 +17,18 @@ limitations under the License. package bootstrapper import ( + "crypto" "net" + "os" + "path" + "github.com/golang/glog" + download "github.com/jimmidyson/go-download" + "github.com/pkg/errors" + "golang.org/x/sync/errgroup" + "k8s.io/minikube/pkg/minikube/assets" "k8s.io/minikube/pkg/minikube/config" + "k8s.io/minikube/pkg/minikube/console" "k8s.io/minikube/pkg/minikube/constants" ) @@ -58,3 +67,63 @@ func GetCachedImageList(version string, bootstrapper string) []string { return []string{} } } + +// CacheBinaries downloads binaries that will be used by UpdateCluster +func CacheBinaries(cr CommandRunner, k8s config.KubernetesConfig) error { + var g errgroup.Group + for _, bin := range []string{"kubelet", "kubeadm"} { + bin := bin + g.Go(func() error { + path, err := maybeDownloadAndCache(bin, k8s.KubernetesVersion) + if err != nil { + return errors.Wrapf(err, "downloading %s", bin) + } + if cr == nil { + return nil + } + f, err := assets.NewFileAsset(path, "/usr/bin", bin, "0641") + if err != nil { + return errors.Wrap(err, "new file asset") + } + if err := cr.Copy(f); err != nil { + return errors.Wrapf(err, "copy") + } + return nil + }) + } + return g.Wait() +} + +func maybeDownloadAndCache(binary, version string) (string, error) { + targetDir := constants.MakeMiniPath("cache", version) + targetFilepath := path.Join(targetDir, binary) + + url := constants.GetKubernetesReleaseURL(binary, version) + + _, err := os.Stat(targetFilepath) + // If it exists, do no verification and continue + if err == nil { + glog.Infof("Not caching binary, using %s", url) + return targetFilepath, nil + } + if !os.IsNotExist(err) { + return "", errors.Wrapf(err, "stat %s version %s at %s", binary, version, targetDir) + } + + if err = os.MkdirAll(targetDir, 0777); err != nil { + return "", errors.Wrapf(err, "mkdir %s", targetDir) + } + + options := download.FileOptions{ + Mkdirs: download.MkdirAll, + } + + options.Checksum = constants.GetKubernetesReleaseURLSha1(binary, version) + options.ChecksumHash = crypto.SHA1 + + console.OutStyle("file-download", "Downloading %s %s", binary, version) + if err := download.ToFile(url, targetFilepath, options); err != nil { + return "", errors.Wrapf(err, "Error downloading %s %s", binary, version) + } + return targetFilepath, nil +} diff --git a/pkg/minikube/bootstrapper/kubeadm/kubeadm.go b/pkg/minikube/bootstrapper/kubeadm/kubeadm.go index aa68d68c0ffa..5b72bab85ade 100644 --- a/pkg/minikube/bootstrapper/kubeadm/kubeadm.go +++ b/pkg/minikube/bootstrapper/kubeadm/kubeadm.go @@ -18,13 +18,10 @@ package kubeadm import ( "bytes" - "crypto" "crypto/tls" "fmt" "net" "net/http" - "os" - "path" "strings" "time" @@ -32,9 +29,7 @@ import ( "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/state" "github.com/golang/glog" - download "github.com/jimmidyson/go-download" "github.com/pkg/errors" - "golang.org/x/sync/errgroup" "k8s.io/minikube/pkg/minikube/assets" "k8s.io/minikube/pkg/minikube/bootstrapper" "k8s.io/minikube/pkg/minikube/config" @@ -348,25 +343,7 @@ func (k *KubeadmBootstrapper) UpdateCluster(cfg config.KubernetesConfig) error { assets.NewMemoryAssetTarget([]byte(defaultCNIConfig), constants.DefaultRktNetConfigPath, "0644")) } - var g errgroup.Group - for _, bin := range []string{"kubelet", "kubeadm"} { - bin := bin - g.Go(func() error { - path, err := maybeDownloadAndCache(bin, cfg.KubernetesVersion) - if err != nil { - return errors.Wrapf(err, "downloading %s", bin) - } - f, err := assets.NewFileAsset(path, "/usr/bin", bin, "0641") - if err != nil { - return errors.Wrap(err, "new file asset") - } - if err := k.c.Copy(f); err != nil { - return errors.Wrapf(err, "copy") - } - return nil - }) - } - if err := g.Wait(); err != nil { + if err := bootstrapper.CacheBinaries(k.c, cfg); err != nil { return errors.Wrap(err, "downloading binaries") } @@ -460,35 +437,3 @@ func generateConfig(k8s config.KubernetesConfig, r cruntime.Manager) (string, er return b.String(), nil } - -func maybeDownloadAndCache(binary, version string) (string, error) { - targetDir := constants.MakeMiniPath("cache", version) - targetFilepath := path.Join(targetDir, binary) - - _, err := os.Stat(targetFilepath) - // If it exists, do no verification and continue - if err == nil { - return targetFilepath, nil - } - if !os.IsNotExist(err) { - return "", errors.Wrapf(err, "stat %s version %s at %s", binary, version, targetDir) - } - - if err = os.MkdirAll(targetDir, 0777); err != nil { - return "", errors.Wrapf(err, "mkdir %s", targetDir) - } - - url := constants.GetKubernetesReleaseURL(binary, version) - options := download.FileOptions{ - Mkdirs: download.MkdirAll, - } - - options.Checksum = constants.GetKubernetesReleaseURLSha1(binary, version) - options.ChecksumHash = crypto.SHA1 - - console.OutStyle("file-download", "Downloading %s %s", binary, version) - if err := download.ToFile(url, targetFilepath, options); err != nil { - return "", errors.Wrapf(err, "Error downloading %s %s", binary, version) - } - return targetFilepath, nil -} diff --git a/pkg/minikube/cluster/cluster.go b/pkg/minikube/cluster/cluster.go index 116822c0055a..a89425e6718c 100644 --- a/pkg/minikube/cluster/cluster.go +++ b/pkg/minikube/cluster/cluster.go @@ -60,6 +60,16 @@ func init() { ssh.SetDefaultClient(ssh.Native) } +// CacheISO downloads and caches ISO. +func CacheISO(api libmachine.API, config cfg.MachineConfig) error { + if config.VMDriver != "none" { + if err := config.Downloader.CacheMinikubeISOFromURL(config.MinikubeISO); err != nil { + return err + } + } + return nil +} + // StartHost starts a host VM. func StartHost(api libmachine.API, config cfg.MachineConfig) (*host.Host, error) { exists, err := api.Exists(cfg.GetMachineName()) @@ -282,10 +292,9 @@ func createHost(api libmachine.API, config cfg.MachineConfig) (*host.Host, error exit.WithError("error getting driver", err) } - if config.VMDriver != "none" { - if err := config.Downloader.CacheMinikubeISOFromURL(config.MinikubeISO); err != nil { - return nil, errors.Wrap(err, "unable to cache ISO") - } + err = CacheISO(api, config) + if err != nil { + return nil, errors.Wrap(err, "unable to cache ISO") } driver := def.ConfigCreator(config) diff --git a/pkg/minikube/console/style.go b/pkg/minikube/console/style.go index d51c863f212a..3a35f95b8aeb 100644 --- a/pkg/minikube/console/style.go +++ b/pkg/minikube/console/style.go @@ -64,6 +64,7 @@ var styles = map[string]style{ "log-entry": {Prefix: " "}, // Indent "crushed": {Prefix: "💔 "}, "url": {Prefix: "👉 "}, + "check": {Prefix: "✔️ "}, // Specialized purpose styles "iso-download": {Prefix: "💿 ", LowPrefix: "@ "},