diff --git a/cmd/minikube/cmd/cache.go b/cmd/minikube/cmd/cache.go new file mode 100644 index 000000000000..8114ae73a300 --- /dev/null +++ b/cmd/minikube/cmd/cache.go @@ -0,0 +1,149 @@ +/* +Copyright 2017 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 ( + "fmt" + "github.com/spf13/cobra" + cmdConfig "k8s.io/minikube/cmd/minikube/cmd/config" + "k8s.io/minikube/pkg/minikube/bootstrapper" + "k8s.io/minikube/pkg/minikube/config" + "k8s.io/minikube/pkg/minikube/constants" + "k8s.io/minikube/pkg/minikube/machine" + "k8s.io/minikube/pkg/minikube/sshutil" + "os" +) + +// cacheCmd represents the cache command +var cacheCmd = &cobra.Command{ + Use: "cache", + Short: "Add or delete an image from the local cache.", + Long: "Add or delete an image from the local cache.", +} + +// addCacheCmd represents the cache add command +var addCacheCmd = &cobra.Command{ + Use: "add", + Short: "Add an image to local cache.", + Long: "Add an image to local cache.", + Run: func(cmd *cobra.Command, args []string) { + // Cache and load images into docker daemon + err := cacheAndLoadImages(args) + if err != nil { + fmt.Fprintf(os.Stderr, "Error caching and loading images: %s\n", err) + os.Exit(1) + } + // Add images to config file + err = cmdConfig.AddToConfigArray("cache", args) + if err != nil { + fmt.Fprintf(os.Stderr, "Error adding cached images to config file: %s\n", err) + os.Exit(1) + } + }, +} + +// deleteCacheCmd represents the cache delete command +var deleteCacheCmd = &cobra.Command{ + Use: "delete", + Short: "Delete an image from the local cache.", + Long: "Delete an image from the local cache.", + Run: func(cmd *cobra.Command, args []string) { + + cmdRunner, err := getCommandRunner() + if err != nil { + fmt.Fprintf(os.Stderr, "Error getting command runner: %s\n", err) + os.Exit(1) + } + // Delete images from docker daemon + err = machine.DeleteImages(cmdRunner, args) + if err != nil { + fmt.Fprintf(os.Stderr, "Error deleting images: %s\n", err) + os.Exit(1) + } + // Delete images from config file + err = cmdConfig.DeleteFromConfigArray("cache", args) + if err != nil { + fmt.Fprintf(os.Stderr, "Error deleting images from config file: %s\n", err) + os.Exit(1) + } + + }, +} + +// LoadCachedImagesInConfigFile loads the images currently in the config file (minikube start) +func LoadCachedImagesInConfigFile() error { + configFile, err := config.ReadConfig() + if err != nil { + return err + } + + values := configFile["cache"] + + if values == nil { + return nil + } + + var images []string + + for _, v := range values.([]interface{}) { + images = append(images, v.(string)) + } + + return cacheAndLoadImages(images) + +} + +func cacheAndLoadImages(images []string) error { + + err := machine.CacheImages(images, constants.ImageCacheDir) + if err != nil { + return err + } + + cmdRunner, err := getCommandRunner() + if err != nil { + return err + } + + return machine.LoadImages(cmdRunner, images, constants.ImageCacheDir) + +} + +func getCommandRunner() (*bootstrapper.SSHRunner, error) { + api, err := machine.NewAPIClient() + if err != nil { + return nil, err + } + defer api.Close() + h, err := api.Load(config.GetMachineName()) + if err != nil { + return nil, err + } + + client, err := sshutil.NewSSHClient(h.Driver) + if err != nil { + return nil, err + } + return bootstrapper.NewSSHRunner(client), nil + +} + +func init() { + cacheCmd.AddCommand(addCacheCmd) + cacheCmd.AddCommand(deleteCacheCmd) + RootCmd.AddCommand(cacheCmd) +} diff --git a/cmd/minikube/cmd/config/config.go b/cmd/minikube/cmd/config/config.go index f4a2affe92fc..73e08fb84415 100644 --- a/cmd/minikube/cmd/config/config.go +++ b/cmd/minikube/cmd/config/config.go @@ -36,6 +36,7 @@ type setFn func(string, string) error type Setting struct { name string set func(config.MinikubeConfig, string, string) error + setArray func(config.MinikubeConfig, string, []string) error validations []setFn callbacks []setFn } @@ -193,6 +194,10 @@ var settings = []Setting{ name: "disable-driver-mounts", set: SetBool, }, + { + name: "cache", + setArray: SetStringArray, + }, } var ConfigCmd = &cobra.Command{ @@ -213,6 +218,79 @@ func configurableFields() string { return strings.Join(fields, "\n") } +// AddToConfigArray adds entries to an array in the config file +func AddToConfigArray(name string, images []string) error { + s, err := findSetting(name) + if err != nil { + return err + } + + // Set the values + configFile, err := config.ReadConfig() + if err != nil { + return err + } + values := configFile[name] + + // Add images to currently existing values in config file + if values != nil { + for _, v := range values.([]interface{}) { + images = append(images, v.(string)) + } + } + + err = s.setArray(configFile, name, images) + if err != nil { + return err + } + + // Write the values + return WriteConfig(configFile) +} + +// DeleteFromConfigArray deletes entries in an array in the config file +func DeleteFromConfigArray(name string, images []string) error { + s, err := findSetting(name) + if err != nil { + return err + } + // Set the values + configFile, err := config.ReadConfig() + if err != nil { + return err + } + values := configFile[name] + if values == nil { + return nil + } + var finalImages []string + + if values != nil { + // Add images that are in config file but not in images to finalImages + // These are the images that should remain in the config file after deletion + for _, v := range values.([]interface{}) { + addImage := true + for _, image := range images { + if v.(string) == image { + addImage = false + } + } + if addImage { + finalImages = append(finalImages, v.(string)) + } + + } + } + + err = s.setArray(configFile, name, finalImages) + if err != nil { + return err + } + + // Write the values + return WriteConfig(configFile) +} + // WriteConfig writes a minikube config to the JSON file func WriteConfig(m config.MinikubeConfig) error { f, err := os.Create(constants.ConfigFile) diff --git a/cmd/minikube/cmd/config/util.go b/cmd/minikube/cmd/config/util.go index 6831aab7d14f..9c2ea2f29371 100644 --- a/cmd/minikube/cmd/config/util.go +++ b/cmd/minikube/cmd/config/util.go @@ -60,6 +60,11 @@ func SetString(m config.MinikubeConfig, name string, val string) error { return nil } +func SetStringArray(m config.MinikubeConfig, name string, val []string) error { + m[name] = val + return nil +} + func SetInt(m config.MinikubeConfig, name string, val string) error { i, err := strconv.Atoi(val) if err != nil { diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index fdd75503f09d..0404c3a44dbe 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -332,6 +332,13 @@ This can also be done automatically by setting the env var CHANGE_MINIKUBE_NONE_ cmdutil.MaybeReportErrorAndExit(err) } } + + fmt.Println("Loading cached images from config file.") + err = LoadCachedImagesInConfigFile() + if err != nil { + fmt.Println("Unable to load cached images from config file.") + } + } func validateK8sVersion(version string) { diff --git a/pkg/minikube/machine/cache_images.go b/pkg/minikube/machine/cache_images.go index 78f81289750e..15f2e91422f2 100644 --- a/pkg/minikube/machine/cache_images.go +++ b/pkg/minikube/machine/cache_images.go @@ -17,6 +17,7 @@ limitations under the License. package machine import ( + "golang.org/x/sync/errgroup" "io/ioutil" "os" "os/exec" @@ -24,8 +25,6 @@ import ( "runtime" "strings" - "golang.org/x/sync/errgroup" - "k8s.io/minikube/pkg/minikube/assets" "k8s.io/minikube/pkg/minikube/bootstrapper" "k8s.io/minikube/pkg/minikube/constants" @@ -98,6 +97,27 @@ func LoadImages(cmd bootstrapper.CommandRunner, images []string, cacheDir string return nil } +// DeleteImages deletes images from local daemon +func DeleteImages(cmd bootstrapper.CommandRunner, images []string) error { + var g errgroup.Group + for _, image := range images { + image := image + g.Go(func() error { + dockerDeleteCmd := "docker rmi " + image + if err := cmd.Run(dockerDeleteCmd); err != nil { + return errors.Wrapf(err, "deleting docker image: %s", image) + } + return nil + }) + } + if err := g.Wait(); err != nil { + return errors.Wrap(err, "deleting cached images") + } + glog.Infoln("Successfully deleted cached images.") + return nil + +} + // # ParseReference cannot have a : in the directory path func sanitizeCacheDir(image string) string { if runtime.GOOS == "windows" && hasWindowsDriveLetter(image) { @@ -185,7 +205,7 @@ func LoadFromCacheBlocking(cmd bootstrapper.CommandRunner, src string) error { return errors.Wrapf(err, "loading docker image: %s", dst) } - if err := cmd.Run("rm -rf " + dst); err != nil { + if err := cmd.Run("sudo rm -rf " + dst); err != nil { return errors.Wrap(err, "deleting temp docker image location") }