Skip to content

Commit

Permalink
Merge pull request #10807 from afbjorklund/load-new
Browse files Browse the repository at this point in the history
Image load: Allow loading local images from tar or cache
  • Loading branch information
medyagh committed Mar 25, 2021
2 parents 894ca12 + 6ce802b commit 77c6de3
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 26 deletions.
86 changes: 80 additions & 6 deletions cmd/minikube/cmd/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@ limitations under the License.
package cmd

import (
"io"
"io/ioutil"
"os"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/exit"
"k8s.io/minikube/pkg/minikube/image"
"k8s.io/minikube/pkg/minikube/machine"
"k8s.io/minikube/pkg/minikube/reason"
)
Expand All @@ -32,11 +38,33 @@ var imageCmd = &cobra.Command{
Long: "Load a local image into minikube",
}

var (
imgDaemon bool
imgRemote bool
)

func saveFile(r io.Reader) (string, error) {
tmp, err := ioutil.TempFile("", "build.*.tar")
if err != nil {
return "", err
}
_, err = io.Copy(tmp, r)
if err != nil {
return "", err
}
err = tmp.Close()
if err != nil {
return "", err
}
return tmp.Name(), nil
}

// loadImageCmd represents the image load command
var loadImageCmd = &cobra.Command{
Use: "load",
Short: "Load a local image into minikube",
Long: "Load a local image into minikube",
Use: "load IMAGE | ARCHIVE | -",
Short: "Load a image into minikube",
Long: "Load a image into minikube",
Example: "minikube image load image\nminikube image load image.tar",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
exit.Message(reason.Usage, "Please provide an image in your local daemon to load into minikube via <minikube image load IMAGE_NAME>")
Expand All @@ -46,13 +74,59 @@ var loadImageCmd = &cobra.Command{
if err != nil {
exit.Error(reason.Usage, "loading profile", err)
}
img := args[0]
if err := machine.CacheAndLoadImages([]string{img}, []*config.Profile{profile}); err != nil {
exit.Error(reason.GuestImageLoad, "Failed to load image", err)

var local bool
if imgRemote || imgDaemon {
local = false
} else {
for _, img := range args {
if img == "-" { // stdin
local = true
imgDaemon = false
imgRemote = false
} else if strings.HasPrefix(img, "/") || strings.HasPrefix(img, ".") {
local = true
imgDaemon = false
imgRemote = false
} else if _, err := os.Stat(img); err == nil {
local = true
imgDaemon = false
imgRemote = false
}
}

if !local {
imgDaemon = true
imgRemote = true
}
}

if args[0] == "-" {
tmp, err := saveFile(os.Stdin)
if err != nil {
exit.Error(reason.GuestImageLoad, "Failed to save stdin", err)
}
args = []string{tmp}
}

if imgDaemon || imgRemote {
image.UseDaemon(imgDaemon)
image.UseRemote(imgRemote)
if err := machine.CacheAndLoadImages(args, []*config.Profile{profile}); err != nil {
exit.Error(reason.GuestImageLoad, "Failed to load image", err)
}
} else if local {
// Load images from local files, without doing any caching or checks in container runtime
// This is similar to tarball.Image but it is done by the container runtime in the cluster.
if err := machine.DoLoadImages(args, []*config.Profile{profile}, ""); err != nil {
exit.Error(reason.GuestImageLoad, "Failed to load image", err)
}
}
},
}

func init() {
imageCmd.AddCommand(loadImageCmd)
loadImageCmd.Flags().BoolVar(&imgDaemon, "daemon", false, "Cache image from docker daemon")
loadImageCmd.Flags().BoolVar(&imgRemote, "remote", false, "Cache image from remote registry")
}
2 changes: 1 addition & 1 deletion pkg/minikube/bootstrapper/kubeadm/kubeadm.go
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ func (k *Bootstrapper) UpdateCluster(cfg config.ClusterConfig) error {
}

if cfg.KubernetesConfig.ShouldLoadCachedImages {
if err := machine.LoadImages(&cfg, k.c, images, constants.ImageCacheDir); err != nil {
if err := machine.LoadCachedImages(&cfg, k.c, images, constants.ImageCacheDir); err != nil {
out.FailureT("Unable to load cached images: {{.error}}", out.V{"error": err})
}
}
Expand Down
53 changes: 46 additions & 7 deletions pkg/minikube/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ var defaultPlatform = v1.Platform{
OS: "linux",
}

var (
useDaemon = true
useRemote = true
)

// UseDaemon is if we should look in local daemon for image ref
func UseDaemon(use bool) {
useDaemon = use
}

// UseRemote is if we should look in remote registry for image ref
func UseRemote(use bool) {
useRemote = use
}

// DigestByDockerLib uses client by docker lib to return image digest
// img.ID in as same as image digest
func DigestByDockerLib(imgClient *client.Client, imgName string) string {
Expand Down Expand Up @@ -198,20 +213,39 @@ func WriteImageToDaemon(img string) error {
}

func retrieveImage(ref name.Reference) (v1.Image, error) {
var err error
var img v1.Image

if !useDaemon && !useRemote {
return nil, fmt.Errorf("neither daemon nor remote")
}

klog.Infof("retrieving image: %+v", ref)
if useDaemon {
img, err = retrieveDaemon(ref)
if err == nil {
return img, nil
}
}
if useRemote {
img, err = retrieveRemote(ref, defaultPlatform)
if err == nil {
return fixPlatform(ref, img, defaultPlatform)
}
}

return nil, err
}

func retrieveDaemon(ref name.Reference) (v1.Image, error) {
img, err := daemon.Image(ref)
if err == nil {
klog.Infof("found %s locally: %+v", ref.Name(), img)
return img, nil
}
// reference does not exist in the local daemon
klog.Infof("daemon lookup for %+v: %v", ref, err)

img, err = retrieveRemote(ref, defaultPlatform)
if err != nil {
return nil, err
}
return fixPlatform(ref, img, defaultPlatform)
return img, err
}

func retrieveRemote(ref name.Reference, p v1.Platform) (v1.Image, error) {
Expand All @@ -221,7 +255,12 @@ func retrieveRemote(ref name.Reference, p v1.Platform) (v1.Image, error) {
}

klog.Warningf("authn lookup for %+v (trying anon): %+v", ref, err)
return remote.Image(ref, remote.WithPlatform(p))
img, err = remote.Image(ref, remote.WithPlatform(p))
// reference does not exist in the remote registry
if err != nil {
klog.Infof("remote lookup for %+v: %v", ref, err)
}
return img, err
}

// See https://github.com/kubernetes/minikube/issues/10402
Expand Down
51 changes: 42 additions & 9 deletions pkg/minikube/machine/cache_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ func CacheImagesForBootstrapper(imageRepository string, version string, clusterB
return nil
}

// LoadImages loads previously cached images into the container runtime
func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string, cacheDir string) error {
// LoadCachedImages loads previously cached images into the container runtime
func LoadCachedImages(cc *config.ClusterConfig, runner command.Runner, images []string, cacheDir string) error {
cr, err := cruntime.New(cruntime.Config{Type: cc.KubernetesConfig.ContainerRuntime, Runner: runner})
if err != nil {
return errors.Wrap(err, "runtime")
Expand All @@ -73,6 +73,7 @@ func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string
klog.Infof("Images are preloaded, skipping loading")
return nil
}

klog.Infof("LoadImages start: %s", images)
start := time.Now()

Expand Down Expand Up @@ -102,7 +103,7 @@ func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string
return nil
}
klog.Infof("%q needs transfer: %v", image, err)
return transferAndLoadImage(runner, cc.KubernetesConfig, image, cacheDir)
return transferAndLoadCachedImage(runner, cc.KubernetesConfig, image, cacheDir)
})
}
if err := g.Wait(); err != nil {
Expand Down Expand Up @@ -157,6 +158,22 @@ func needsTransfer(imgClient *client.Client, imgName string, cr cruntime.Manager
return nil
}

// LoadLocalImages loads images into the container runtime
func LoadLocalImages(cc *config.ClusterConfig, runner command.Runner, images []string) error {
var g errgroup.Group
for _, image := range images {
image := image
g.Go(func() error {
return transferAndLoadImage(runner, cc.KubernetesConfig, image)
})
}
if err := g.Wait(); err != nil {
return errors.Wrap(err, "loading images")
}
klog.Infoln("Successfully loaded all images")
return nil
}

// CacheAndLoadImages caches and loads images to all profiles
func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
if len(images) == 0 {
Expand All @@ -168,6 +185,11 @@ func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
return errors.Wrap(err, "save to dir")
}

return DoLoadImages(images, profiles, constants.ImageCacheDir)
}

// DoLoadImages loads images to all profiles
func DoLoadImages(images []string, profiles []*config.Profile, cacheDir string) error {
api, err := NewAPIClient()
if err != nil {
return errors.Wrap(err, "api")
Expand Down Expand Up @@ -209,7 +231,13 @@ func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
if err != nil {
return err
}
err = LoadImages(c, cr, images, constants.ImageCacheDir)
if cacheDir != "" {
// loading image names, from cache
err = LoadCachedImages(c, cr, images, cacheDir)
} else {
// loading image files
err = LoadLocalImages(c, cr, images)
}
if err != nil {
failed = append(failed, m)
klog.Warningf("Failed to load cached images for profile %s. make sure the profile is running. %v", pName, err)
Expand All @@ -226,15 +254,20 @@ func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
return nil
}

// transferAndLoadImage transfers and loads a single image from the cache
func transferAndLoadImage(cr command.Runner, k8s config.KubernetesConfig, imgName string, cacheDir string) error {
// transferAndLoadCachedImage transfers and loads a single image from the cache
func transferAndLoadCachedImage(cr command.Runner, k8s config.KubernetesConfig, imgName string, cacheDir string) error {
src := filepath.Join(cacheDir, imgName)
src = localpath.SanitizeCacheDir(src)
return transferAndLoadImage(cr, k8s, src)
}

// transferAndLoadImage transfers and loads a single image
func transferAndLoadImage(cr command.Runner, k8s config.KubernetesConfig, src string) error {
r, err := cruntime.New(cruntime.Config{Type: k8s.ContainerRuntime, Runner: cr})
if err != nil {
return errors.Wrap(err, "runtime")
}
src := filepath.Join(cacheDir, imgName)
src = localpath.SanitizeCacheDir(src)
klog.Infof("Loading image from cache: %s", src)
klog.Infof("Loading image from: %s", src)
filename := filepath.Base(src)
if _, err := os.Stat(src); err != nil {
return err
Expand Down
20 changes: 17 additions & 3 deletions site/content/en/docs/commands/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,28 @@ minikube image help [command] [flags]

## minikube image load

Load a local image into minikube
Load a image into minikube

### Synopsis

Load a local image into minikube
Load a image into minikube

```shell
minikube image load [flags]
minikube image load IMAGE | ARCHIVE | - [flags]
```

### Examples

```
minikube image load image
minikube image load image.tar
```

### Options

```
--daemon Cache image from docker daemon
--remote Cache image from remote registry
```

### Options inherited from parent commands
Expand Down

0 comments on commit 77c6de3

Please sign in to comment.