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

Implement minikube image load command #10366

Merged
merged 15 commits into from
Feb 9, 2021
34 changes: 32 additions & 2 deletions cmd/minikube/cmd/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,25 @@ package cmd

import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/klog/v2"
cmdConfig "k8s.io/minikube/cmd/minikube/cmd/config"
"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/node"
"k8s.io/minikube/pkg/minikube/out"
"k8s.io/minikube/pkg/minikube/reason"
)

// cacheImageConfigKey is the config field name used to store which images we have previously cached
const cacheImageConfigKey = "cache"

var (
all string
)

// cacheCmd represents the cache command
var cacheCmd = &cobra.Command{
Use: "cache",
Expand All @@ -42,8 +50,9 @@ var addCacheCmd = &cobra.Command{
Short: "Add an image to local cache.",
Long: "Add an image to local cache.",
Run: func(cmd *cobra.Command, args []string) {
out.WarningT("\"minikube cache\" will be deprecated in upcoming versions, please switch to \"minikube image load\"")
// Cache and load images into docker daemon
if err := machine.CacheAndLoadImages(args); err != nil {
if err := machine.CacheAndLoadImages(args, cacheAddProfiles()); err != nil {
exit.Error(reason.InternalCacheLoad, "Failed to cache and load images", err)
}
// Add images to config file
Expand All @@ -53,6 +62,26 @@ var addCacheCmd = &cobra.Command{
},
}

func addCacheCmdFlags() {
addCacheCmd.Flags().Bool(all, false, "Add image to cache for all running minikube clusters")
}

func cacheAddProfiles() []*config.Profile {
if viper.GetBool(all) {
validProfiles, _, err := config.ListProfiles() // need to load image to all profiles
if err != nil {
klog.Warningf("error listing profiles: %v", err)
}
return validProfiles
}
profile := viper.GetString(config.ProfileName)
p, err := config.LoadProfile(profile)
if err != nil {
exit.Message(reason.Usage, "{{.profile}} profile is not valid: {{.err}}", out.V{"profile": profile, "err": err})
}
return []*config.Profile{p}
}

// deleteCacheCmd represents the cache delete command
var deleteCacheCmd = &cobra.Command{
Use: "delete",
Expand All @@ -76,14 +105,15 @@ var reloadCacheCmd = &cobra.Command{
Short: "reload cached images.",
Long: "reloads images previously added using the 'cache add' subcommand",
Run: func(cmd *cobra.Command, args []string) {
err := node.CacheAndLoadImagesInConfig()
err := node.CacheAndLoadImagesInConfig(cacheAddProfiles())
if err != nil {
exit.Error(reason.GuestCacheLoad, "Failed to reload cached images", err)
}
},
}

func init() {
addCacheCmdFlags()
cacheCmd.AddCommand(addCacheCmd)
cacheCmd.AddCommand(deleteCacheCmd)
cacheCmd.AddCommand(reloadCacheCmd)
Expand Down
58 changes: 58 additions & 0 deletions cmd/minikube/cmd/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
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 (
"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/machine"
"k8s.io/minikube/pkg/minikube/reason"
)

// imageCmd represents the image command
var imageCmd = &cobra.Command{
priyawadhwa marked this conversation as resolved.
Show resolved Hide resolved
Use: "image",
Short: "Load a local image into minikube",
Long: "Load a local image into minikube",
}

// 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",
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>")
}
// Cache and load images into docker daemon
profile, err := config.LoadProfile(viper.GetString(config.ProfileName))
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)
}
},
}

func init() {
imageCmd.AddCommand(loadImageCmd)
}
1 change: 1 addition & 0 deletions cmd/minikube/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ func init() {
dockerEnvCmd,
podmanEnvCmd,
cacheCmd,
imageCmd,
},
},
{
Expand Down
6 changes: 1 addition & 5 deletions pkg/minikube/machine/cache_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func needsTransfer(imgClient *client.Client, imgName string, cr cruntime.Manager
}

// CacheAndLoadImages caches and loads images to all profiles
func CacheAndLoadImages(images []string) error {
func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
if len(images) == 0 {
return nil
}
Expand All @@ -173,10 +173,6 @@ func CacheAndLoadImages(images []string) error {
return errors.Wrap(err, "api")
}
defer api.Close()
profiles, _, err := config.ListProfiles() // need to load image to all profiles
if err != nil {
return errors.Wrap(err, "list profiles")
}

succeeded := []string{}
failed := []string{}
Expand Down
4 changes: 2 additions & 2 deletions pkg/minikube/node/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,15 @@ func saveImagesToTarFromConfig() error {

// CacheAndLoadImagesInConfig loads the images currently in the config file
// called by 'start' and 'cache reload' commands.
func CacheAndLoadImagesInConfig() error {
func CacheAndLoadImagesInConfig(profiles []*config.Profile) error {
images, err := imagesInConfigFile()
if err != nil {
return errors.Wrap(err, "images")
}
if len(images) == 0 {
return nil
}
return machine.CacheAndLoadImages(images)
return machine.CacheAndLoadImages(images, profiles)
}

func imagesInConfigFile() ([]string, error) {
Expand Down
8 changes: 6 additions & 2 deletions pkg/minikube/node/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,14 @@ func Start(starter Starter, apiServer bool) (*kubeconfig.Settings, error) {

wg.Add(1)
go func() {
if err := CacheAndLoadImagesInConfig(); err != nil {
defer wg.Done()
profile, err := config.LoadProfile(starter.Cfg.Name)
if err != nil {
out.FailureT("Unable to load profile: {{.error}}", out.V{"error": err})
}
if err := CacheAndLoadImagesInConfig([]*config.Profile{profile}); err != nil {
out.FailureT("Unable to push cached images: {{.error}}", out.V{"error": err})
}
wg.Done()
}()

// enable addons, both old and new!
Expand Down
1 change: 1 addition & 0 deletions pkg/minikube/reason/reason.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ var (
GuestCert = Kind{ID: "GUEST_CERT", ExitCode: ExGuestError}
GuestCpConfig = Kind{ID: "GUEST_CP_CONFIG", ExitCode: ExGuestConfig}
GuestDeletion = Kind{ID: "GUEST_DELETION", ExitCode: ExGuestError}
GuestImageLoad = Kind{ID: "GUEST_IMAGE_LOAD", ExitCode: ExGuestError}
GuestLoadHost = Kind{ID: "GUEST_LOAD_HOST", ExitCode: ExGuestError}
GuestMount = Kind{ID: "GUEST_MOUNT", ExitCode: ExGuestError}
GuestMountConflict = Kind{ID: "GUEST_MOUNT_CONFLICT", ExitCode: ExGuestConflict}
Expand Down
6 changes: 6 additions & 0 deletions site/content/en/docs/commands/cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ Add an image to local cache.
minikube cache add [flags]
```

### Options

```
-- Add image to cache for all running minikube clusters
```

### Options inherited from parent commands

```
Expand Down
106 changes: 106 additions & 0 deletions site/content/en/docs/commands/image.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
title: "image"
description: >
Load a local image into minikube
---


## minikube image

Load a local image into minikube

### Synopsis

Load a local image into minikube

### Options inherited from parent commands

```
--add_dir_header If true, adds the file directory to the header of the log messages
--alsologtostderr log to standard error as well as files
-b, --bootstrapper string The name of the cluster bootstrapper that will set up the Kubernetes cluster. (default "kubeadm")
-h, --help
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--log_file string If non-empty, use this log file
--log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
--logtostderr log to standard error instead of files
--one_output If true, only write logs to their native severity level (vs also writing to each lower severity level
-p, --profile string The name of the minikube VM being used. This can be set to allow having multiple instances of minikube independently. (default "minikube")
--skip_headers If true, avoid header prefixes in the log messages
--skip_log_headers If true, avoid headers when opening log files
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
--user string Specifies the user executing the operation. Useful for auditing operations executed by 3rd party tools. Defaults to the operating system username.
-v, --v Level number for the log level verbosity
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```

## minikube image help

Help about any command

### Synopsis

Help provides help for any command in the application.
Simply type image help [path to command] for full details.

```shell
minikube image help [command] [flags]
```

### Options inherited from parent commands

```
--add_dir_header If true, adds the file directory to the header of the log messages
--alsologtostderr log to standard error as well as files
-b, --bootstrapper string The name of the cluster bootstrapper that will set up the Kubernetes cluster. (default "kubeadm")
-h, --help
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--log_file string If non-empty, use this log file
--log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
--logtostderr log to standard error instead of files
--one_output If true, only write logs to their native severity level (vs also writing to each lower severity level
-p, --profile string The name of the minikube VM being used. This can be set to allow having multiple instances of minikube independently. (default "minikube")
--skip_headers If true, avoid header prefixes in the log messages
--skip_log_headers If true, avoid headers when opening log files
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
--user string Specifies the user executing the operation. Useful for auditing operations executed by 3rd party tools. Defaults to the operating system username.
-v, --v Level number for the log level verbosity
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```

## minikube image load

Load a local image into minikube

### Synopsis

Load a local image into minikube

```shell
minikube image load [flags]
```

### Options inherited from parent commands

```
--add_dir_header If true, adds the file directory to the header of the log messages
--alsologtostderr log to standard error as well as files
-b, --bootstrapper string The name of the cluster bootstrapper that will set up the Kubernetes cluster. (default "kubeadm")
-h, --help
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--log_file string If non-empty, use this log file
--log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
--logtostderr log to standard error instead of files
--one_output If true, only write logs to their native severity level (vs also writing to each lower severity level
-p, --profile string The name of the minikube VM being used. This can be set to allow having multiple instances of minikube independently. (default "minikube")
--skip_headers If true, avoid header prefixes in the log messages
--skip_log_headers If true, avoid headers when opening log files
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
--user string Specifies the user executing the operation. Useful for auditing operations executed by 3rd party tools. Defaults to the operating system username.
-v, --v Level number for the log level verbosity
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```

43 changes: 43 additions & 0 deletions test/integration/functional_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ func TestFunctional(t *testing.T) {
{"UpdateContextCmd", validateUpdateContextCmd},
{"DockerEnv", validateDockerEnv},
{"NodeLabels", validateNodeLabels},
{"LoadImage", validateLoadImage},
}
for _, tc := range tests {
tc := tc
Expand Down Expand Up @@ -158,6 +159,48 @@ func validateNodeLabels(ctx context.Context, t *testing.T, profile string) {
}
}

// validateLoadImage makes sure that `minikube load image` works as expected
func validateLoadImage(ctx context.Context, t *testing.T, profile string) {
if NoneDriver() {
t.Skip("load image not available on none driver")
}
defer PostMortemLogs(t, profile)
// pull busybox
busybox := "busybox:latest"
rr, err := Run(t, exec.CommandContext(ctx, "docker", "pull", busybox))
if err != nil {
t.Fatalf("starting minikube: %v\n%s", err, rr.Output())
}

// tag busybox
newImage := fmt.Sprintf("busybox:%s", profile)
rr, err = Run(t, exec.CommandContext(ctx, "docker", "tag", busybox, newImage))
if err != nil {
t.Fatalf("starting minikube: %v\n%s", err, rr.Output())
}

// try to load the new image into minikube
rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "image", "load", newImage))
if err != nil {
t.Fatalf("loading image into minikube: %v\n%s", err, rr.Output())
}

// make sure the image was correctly loaded
var cmd *exec.Cmd
if ContainerRuntime() == "docker" {
cmd = exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "docker", "images", "--format", "{{.Repository}}:{{.Tag}}")
} else {
cmd = exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "sudo", "ctr", "-n=k8s.io", "image", "ls")
}
rr, err = Run(t, cmd)
if err != nil {
t.Fatalf("listing images: %v\n%s", err, rr.Output())
}
if !strings.Contains(rr.Output(), newImage) {
t.Fatalf("expected %s to be loaded into minikube but the image is not there", newImage)
}
}

// check functionality of minikube after evaling docker-env
// TODO: Add validatePodmanEnv for crio runtime: #10231
func validateDockerEnv(ctx context.Context, t *testing.T, profile string) {
Expand Down