diff --git a/cmd/minikube/cmd/cache.go b/cmd/minikube/cmd/cache.go index b6fdb246cab2..7b403f4b904f 100644 --- a/cmd/minikube/cmd/cache.go +++ b/cmd/minikube/cmd/cache.go @@ -66,20 +66,43 @@ var deleteCacheCmd = &cobra.Command{ }, } -// LoadCachedImagesInConfigFile loads the images currently in the config file (minikube start) -func LoadCachedImagesInConfigFile() error { +func imagesInConfigFile() ([]string, error) { configFile, err := config.ReadConfig() if err != nil { - return err + return nil, err } if values, ok := configFile[constants.Cache]; ok { var images []string for key := range values.(map[string]interface{}) { images = append(images, key) } - return machine.CacheAndLoadImages(images) + return images, nil + } + return []string{}, nil +} + +// CacheImagesInConfigFile caches the images currently in the config file (minikube start) +func CacheImagesInConfigFile() error { + images, err := imagesInConfigFile() + if err != nil { + return err + } + if len(images) == 0 { + return nil + } + return machine.CacheImages(images, constants.ImageCacheDir) +} + +// LoadCachedImagesInConfigFile loads the images currently in the config file (minikube start) +func LoadCachedImagesInConfigFile() error { + images, err := imagesInConfigFile() + if err != nil { + return err + } + if len(images) == 0 { + return nil } - return nil + return machine.CacheAndLoadImages(images) } func init() { diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index 142cb24383ce..5b170d9022c8 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -87,6 +87,7 @@ const ( hidden = "hidden" embedCerts = "embed-certs" noVTXCheck = "no-vtx-check" + downloadOnly = "download-only" ) var ( @@ -135,6 +136,7 @@ func init() { startCmd.Flags().String(networkPlugin, "", "The name of the network plugin") startCmd.Flags().Bool(enableDefaultCNI, false, "Enable the default CNI plugin (/etc/cni/net.d/k8s.conf). Used in conjunction with \"--network-plugin=cni\"") startCmd.Flags().String(featureGates, "", "A set of key=value pairs that describe feature gates for alpha/experimental features.") + startCmd.Flags().Bool(downloadOnly, false, "If true, only download and cache files for later use - don't install or start anything.") startCmd.Flags().Bool(cacheImages, true, "If true, cache docker images for the current bootstrapper and load them into the machine. Always false with --vm-driver=none.") startCmd.Flags().Var(&extraOptions, "extra-config", `A set of key=value pairs that describe configuration that may be passed to different components. @@ -192,6 +194,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(config.MachineConfig); err != nil { + exit.WithError("Failed to cache ISO", err) + } + if err := doCacheBinaries(k8sVersion); err != nil { + exit.WithError("Failed to cache binaries", err) + } + waitCacheImages(&cacheGroup) + if err := CacheImagesInConfigFile(); err != nil { + exit.WithError("Failed to cache images", err) + } + console.OutStyle("check", "Download complete!") + return + } + host, preexisting := startHost(m, config.MachineConfig) ip := validateNetwork(host) @@ -260,6 +278,11 @@ func validateConfig() { } } +// doCacheBinaries caches Kubernetes binaries in the foreground +func doCacheBinaries(k8sVersion string) error { + return machine.CacheBinariesForBootstrapper(k8sVersion, viper.GetString(cmdcfg.Bootstrapper)) +} + // beginCacheImages caches Docker images in the background func beginCacheImages(g *errgroup.Group, k8sVersion string) { if !viper.GetBool(cacheImages) { diff --git a/pkg/minikube/bootstrapper/bootstrapper.go b/pkg/minikube/bootstrapper/bootstrapper.go index 2447a46ac431..e69d79058202 100644 --- a/pkg/minikube/bootstrapper/bootstrapper.go +++ b/pkg/minikube/bootstrapper/bootstrapper.go @@ -51,6 +51,16 @@ const ( BootstrapperTypeKubeadm = "kubeadm" ) +// GetCachedBinaryList returns the list of binaries +func GetCachedBinaryList(bootstrapper string) []string { + switch bootstrapper { + case BootstrapperTypeKubeadm: + return constants.GetKubeadmCachedBinaries() + default: + return []string{} + } +} + // GetCachedImageList returns the list of images for a version func GetCachedImageList(imageRepository string, version string, bootstrapper string) []string { switch bootstrapper { diff --git a/pkg/minikube/bootstrapper/kubeadm/kubeadm.go b/pkg/minikube/bootstrapper/kubeadm/kubeadm.go index cd40886656e5..8e077dd71572 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,7 +29,6 @@ import ( "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/state" "github.com/golang/glog" - "github.com/jimmidyson/go-download" "github.com/pkg/errors" "golang.org/x/sync/errgroup" "k8s.io/apimachinery/pkg/labels" @@ -466,19 +462,16 @@ func (k *Bootstrapper) UpdateCluster(cfg config.KubernetesConfig) error { } var g errgroup.Group - for _, bin := range []string{"kubelet", "kubeadm"} { + for _, bin := range constants.GetKubeadmCachedBinaries() { bin := bin g.Go(func() error { - path, err := maybeDownloadAndCache(bin, cfg.KubernetesVersion) + path, err := machine.CacheBinary(bin, cfg.KubernetesVersion) if err != nil { return errors.Wrapf(err, "downloading %s", bin) } - f, err := assets.NewFileAsset(path, "/usr/bin", bin, "0641") + err = machine.CopyBinary(k.c, bin, path) if err != nil { - return errors.Wrap(err, "new file asset") - } - if err := k.c.Copy(f); err != nil { - return errors.Wrapf(err, "copy") + return errors.Wrapf(err, "copying %s", bin) } return nil }) @@ -583,35 +576,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 4ad5aa3d2192..cf500b7868db 100644 --- a/pkg/minikube/cluster/cluster.go +++ b/pkg/minikube/cluster/cluster.go @@ -58,6 +58,16 @@ func init() { ssh.SetDefaultClient(ssh.Native) } +// CacheISO downloads and caches ISO. +func CacheISO(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()) @@ -280,10 +290,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(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 49eec5e80002..adddc4bc6591 100644 --- a/pkg/minikube/console/style.go +++ b/pkg/minikube/console/style.go @@ -69,6 +69,7 @@ var styles = map[string]style{ "documentation": {Prefix: "📘 "}, "issues": {Prefix: "⁉️ "}, "issue": {Prefix: " ▪ "}, // Indented bullet + "check": {Prefix: "✔️ "}, // Specialized purpose styles "iso-download": {Prefix: "💿 ", LowPrefix: "@ "}, diff --git a/pkg/minikube/constants/constants.go b/pkg/minikube/constants/constants.go index d46834fed072..a26c957be0bc 100644 --- a/pkg/minikube/constants/constants.go +++ b/pkg/minikube/constants/constants.go @@ -230,6 +230,11 @@ const DriverNone = "none" // FileScheme is the file scheme const FileScheme = "file" +// GetKubeadmCachedBinaries gets the binaries to cache for kubeadm +func GetKubeadmCachedBinaries() []string { + return []string{"kubelet", "kubeadm"} +} + // GetKubeadmCachedImages gets the images to cache for kubeadm for a version func GetKubeadmCachedImages(imageRepository string, kubernetesVersionStr string) (string, []string) { minikubeRepository := imageRepository diff --git a/pkg/minikube/machine/cache_binaries.go b/pkg/minikube/machine/cache_binaries.go new file mode 100644 index 000000000000..b21c76d85a0f --- /dev/null +++ b/pkg/minikube/machine/cache_binaries.go @@ -0,0 +1,96 @@ +/* +Copyright 2016 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 machine + +import ( + "crypto" + "os" + "path" + + "github.com/golang/glog" + "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/console" + "k8s.io/minikube/pkg/minikube/constants" +) + +// CacheBinariesForBootstrapper will cache binaries for a bootstrapper +func CacheBinariesForBootstrapper(version string, clusterBootstrapper string) error { + binaries := bootstrapper.GetCachedBinaryList(clusterBootstrapper) + + var g errgroup.Group + for _, bin := range binaries { + bin := bin + g.Go(func() error { + if _, err := CacheBinary(bin, version); err != nil { + return errors.Wrapf(err, "caching image %s", bin) + } + return nil + }) + } + return g.Wait() +} + +// CacheBinary will cache a binary on the host +func CacheBinary(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 +} + +// CopyBinary copies previously cached binaries into the path +func CopyBinary(cr bootstrapper.CommandRunner, binary, path string) error { + f, err := assets.NewFileAsset(path, "/usr/bin", binary, "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 +}