Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cache command to cache non-minikube images #2203

Merged
merged 4 commits into from
Nov 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions cmd/minikube/cmd/cache.go
Original file line number Diff line number Diff line change
@@ -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)
}
58 changes: 58 additions & 0 deletions cmd/minikube/cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -193,6 +194,11 @@ var settings = []Setting{
name: "disable-driver-mounts",
set: SetBool,
},
{
name: "cache",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also needs a set method, since thats what minikube config set uses. Otherwise, it will panic!

diff --git a/cmd/minikube/cmd/config/config.go b/cmd/minikube/cmd/config/config.go
index 15bad2c56..582027d5a 100644
--- a/cmd/minikube/cmd/config/config.go
+++ b/cmd/minikube/cmd/config/config.go
@@ -196,6 +196,7 @@ var settings = []Setting{
        },
        {
                name:   "cache",
+               set:    SetConfigMap,
                setMap: SetMap,
        },
 }
diff --git a/cmd/minikube/cmd/config/util.go b/cmd/minikube/cmd/config/util.go
index 5a2db1d7a..212771ae3 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"
@@ -65,6 +66,16 @@ func SetMap(m config.MinikubeConfig, name string, val map[string]interface{}) er
        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 {

set: SetConfigMap,
setMap: SetMap,
},
}

var ConfigCmd = &cobra.Command{
Expand All @@ -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)
Expand Down
16 changes: 16 additions & 0 deletions cmd/minikube/cmd/config/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"os"
"strconv"
"strings"

"github.com/pkg/errors"
"k8s.io/minikube/pkg/minikube/assets"
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions cmd/minikube/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions pkg/minikube/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()}
Expand Down
70 changes: 67 additions & 3 deletions pkg/minikube/machine/cache_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ limitations under the License.
package machine
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I supposed this never worked, but we never cared about checking the error since it was done on a best-effort basis. But deleting the temp image tarballs in the VM requires sudo

diff --git a/pkg/minikube/machine/cache_images.go b/pkg/minikube/machine/cache_images.go
index ec26c0230..63cf4e40a 100644
--- a/pkg/minikube/machine/cache_images.go
+++ b/pkg/minikube/machine/cache_images.go
@@ -17,7 +17,6 @@ limitations under the License.
 package machine

 import (
-       "golang.org/x/sync/errgroup"
        "io/ioutil"
        "os"
        "os/exec"
@@ -25,6 +24,8 @@ 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"
@@ -212,7 +213,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")
        }


import (
"golang.org/x/sync/errgroup"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -185,14 +212,51 @@ 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need sudo?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because its root owned and the command runner runs as the docker user.

return errors.Wrap(err, "deleting temp docker image location")
}

glog.Infof("Successfully loaded image %s from cache", src)
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 {
Expand Down