diff --git a/cmd/minikube/cmd/cache.go b/cmd/minikube/cmd/cache.go new file mode 100644 index 000000000000..65b16f5ab584 --- /dev/null +++ b/cmd/minikube/cmd/cache.go @@ -0,0 +1,94 @@ +/* +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/config" + "k8s.io/minikube/pkg/minikube/constants" + "k8s.io/minikube/pkg/minikube/machine" + "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 + if err := machine.CacheAndLoadImages(args); err != nil { + fmt.Fprintf(os.Stderr, "Error caching and loading images: %s\n", err) + os.Exit(1) + } + // Add images to config file + if err := cmdConfig.AddToConfigMap(constants.Cache, args); 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) { + // Delete images from config file + if err := cmdConfig.DeleteFromConfigMap(constants.Cache, args); err != nil { + fmt.Fprintf(os.Stderr, "Error deleting images from config file: %s\n", err) + os.Exit(1) + } + // Delete images from cache/images directory + if err := machine.DeleteFromImageCacheDir(args); err != nil { + fmt.Fprintf(os.Stderr, "Error deleting images: %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 + } + 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 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..582027d5a7d7 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 + setMap func(config.MinikubeConfig, string, map[string]interface{}) error validations []setFn callbacks []setFn } @@ -193,6 +194,11 @@ var settings = []Setting{ name: "disable-driver-mounts", set: SetBool, }, + { + name: "cache", + set: SetConfigMap, + setMap: SetMap, + }, } var ConfigCmd = &cobra.Command{ @@ -213,6 +219,58 @@ func configurableFields() string { return strings.Join(fields, "\n") } +// AddToConfigMap adds entries to a map in the config file +func AddToConfigMap(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 + } + newImages := make(map[string]interface{}) + for _, image := range images { + newImages[image] = nil + } + if values, ok := configFile[name].(map[string]interface{}); ok { + for key := range values { + newImages[key] = nil + } + } + if err = s.setMap(configFile, name, newImages); err != nil { + return err + } + // Write the values + return WriteConfig(configFile) +} + +// DeleteFromConfigMap deletes entries from a map in the config file +func DeleteFromConfigMap(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, ok := configFile[name] + if !ok { + return nil + } + for _, image := range images { + delete(values.(map[string]interface{}), image) + } + if err = s.setMap(configFile, name, values.(map[string]interface{})); 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..212771ae33eb 100644 --- a/cmd/minikube/cmd/config/util.go +++ b/cmd/minikube/cmd/config/util.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "strconv" + "strings" "github.com/pkg/errors" "k8s.io/minikube/pkg/minikube/assets" @@ -60,6 +61,21 @@ func SetString(m config.MinikubeConfig, name string, val string) error { return nil } +func SetMap(m config.MinikubeConfig, name string, val map[string]interface{}) error { + m[name] = val + return nil +} + +func SetConfigMap(m config.MinikubeConfig, name string, val string) error { + list := strings.Split(val, ",") + v := make(map[string]interface{}) + for _, s := range list { + v[s] = nil + } + m[name] = v + 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..7739db35142d 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -332,6 +332,12 @@ 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/constants/constants.go b/pkg/minikube/constants/constants.go index 7d33e665eb3b..e091aa60290d 100644 --- a/pkg/minikube/constants/constants.go +++ b/pkg/minikube/constants/constants.go @@ -66,6 +66,9 @@ const DefaultMachineName = "minikube" // The name of the default storage class provisioner const DefaultStorageClassProvisioner = "standard" +// Used to modify the cache field in the config file +const Cache = "cache" + // MakeMiniPath is a utility to calculate a relative path to our directory. func MakeMiniPath(fileName ...string) string { args := []string{GetMinipath()} diff --git a/pkg/minikube/machine/cache_images.go b/pkg/minikube/machine/cache_images.go index 78f81289750e..2d3c4ec33529 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,11 +25,11 @@ 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/config" "k8s.io/minikube/pkg/minikube/constants" + "k8s.io/minikube/pkg/minikube/sshutil" "github.com/containers/image/copy" "github.com/containers/image/docker" @@ -98,6 +99,32 @@ func LoadImages(cmd bootstrapper.CommandRunner, images []string, cacheDir string return nil } +func CacheAndLoadImages(images []string) error { + if err := CacheImages(images, constants.ImageCacheDir); err != nil { + return err + } + api, err := NewAPIClient() + if err != nil { + return err + } + defer api.Close() + h, err := api.Load(config.GetMachineName()) + if err != nil { + return err + } + + client, err := sshutil.NewSSHClient(h.Driver) + if err != nil { + return err + } + cmdRunner, err := bootstrapper.NewSSHRunner(client), nil + if err != nil { + return err + } + + return LoadImages(cmdRunner, images, constants.ImageCacheDir) +} + // # ParseReference cannot have a : in the directory path func sanitizeCacheDir(image string) string { if runtime.GOOS == "windows" && hasWindowsDriveLetter(image) { @@ -185,7 +212,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") } @@ -193,6 +220,43 @@ func LoadFromCacheBlocking(cmd bootstrapper.CommandRunner, src string) error { return nil } +func DeleteFromImageCacheDir(images []string) error { + for _, image := range images { + path := filepath.Join(constants.ImageCacheDir, image) + glog.Infoln("Deleting image in cache at ", path) + if err := os.Remove(path); err != nil { + return err + } + } + return cleanImageCacheDir() +} + +func cleanImageCacheDir() error { + err := filepath.Walk(constants.ImageCacheDir, func(path string, info os.FileInfo, err error) error { + // If error is not nil, it's because the path was already deleted and doesn't exist + // Move on to next path + if err != nil { + return nil + } + // Check if path is directory + if !info.IsDir() { + return nil + } + // If directory is empty, delete it + entries, err := ioutil.ReadDir(path) + if err != nil { + return err + } + if len(entries) == 0 { + if err = os.Remove(path); err != nil { + return err + } + } + return nil + }) + return err +} + func getSrcRef(image string) (types.ImageReference, error) { srcRef, err := docker.ParseReference("//" + image) if err != nil {