diff --git a/cmd/minikube/cmd/cache.go b/cmd/minikube/cmd/cache.go index ff1ab11568d9..7933f4f7ec39 100644 --- a/cmd/minikube/cmd/cache.go +++ b/cmd/minikube/cmd/cache.go @@ -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", @@ -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 @@ -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", @@ -76,7 +105,7 @@ 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) } @@ -84,6 +113,7 @@ var reloadCacheCmd = &cobra.Command{ } func init() { + addCacheCmdFlags() cacheCmd.AddCommand(addCacheCmd) cacheCmd.AddCommand(deleteCacheCmd) cacheCmd.AddCommand(reloadCacheCmd) diff --git a/cmd/minikube/cmd/image.go b/cmd/minikube/cmd/image.go new file mode 100644 index 000000000000..3186c7120a76 --- /dev/null +++ b/cmd/minikube/cmd/image.go @@ -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{ + 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 ") + } + // 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) +} diff --git a/cmd/minikube/cmd/root.go b/cmd/minikube/cmd/root.go index a29886b5b207..17850e985b82 100644 --- a/cmd/minikube/cmd/root.go +++ b/cmd/minikube/cmd/root.go @@ -201,6 +201,7 @@ func init() { dockerEnvCmd, podmanEnvCmd, cacheCmd, + imageCmd, }, }, { diff --git a/pkg/minikube/machine/cache_images.go b/pkg/minikube/machine/cache_images.go index 3b5f2d379e53..1288e7117bf2 100644 --- a/pkg/minikube/machine/cache_images.go +++ b/pkg/minikube/machine/cache_images.go @@ -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 } @@ -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{} diff --git a/pkg/minikube/node/cache.go b/pkg/minikube/node/cache.go index e775ed3ca104..0abcb26aa09a 100644 --- a/pkg/minikube/node/cache.go +++ b/pkg/minikube/node/cache.go @@ -210,7 +210,7 @@ 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") @@ -218,7 +218,7 @@ func CacheAndLoadImagesInConfig() error { if len(images) == 0 { return nil } - return machine.CacheAndLoadImages(images) + return machine.CacheAndLoadImages(images, profiles) } func imagesInConfigFile() ([]string, error) { diff --git a/pkg/minikube/node/start.go b/pkg/minikube/node/start.go index d1081fd7bb70..3dbbb6fff956 100644 --- a/pkg/minikube/node/start.go +++ b/pkg/minikube/node/start.go @@ -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! diff --git a/pkg/minikube/reason/reason.go b/pkg/minikube/reason/reason.go index ee406919d95b..7010168d27d3 100644 --- a/pkg/minikube/reason/reason.go +++ b/pkg/minikube/reason/reason.go @@ -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} diff --git a/site/content/en/docs/commands/cache.md b/site/content/en/docs/commands/cache.md index 6f7eff26a93c..39065402f700 100644 --- a/site/content/en/docs/commands/cache.md +++ b/site/content/en/docs/commands/cache.md @@ -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 ``` diff --git a/site/content/en/docs/commands/image.md b/site/content/en/docs/commands/image.md new file mode 100644 index 000000000000..e98ceb45ebb7 --- /dev/null +++ b/site/content/en/docs/commands/image.md @@ -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 +``` + diff --git a/test/integration/functional_test.go b/test/integration/functional_test.go index d52ae2f864c7..aca25d07215a 100644 --- a/test/integration/functional_test.go +++ b/test/integration/functional_test.go @@ -127,6 +127,7 @@ func TestFunctional(t *testing.T) { {"UpdateContextCmd", validateUpdateContextCmd}, {"DockerEnv", validateDockerEnv}, {"NodeLabels", validateNodeLabels}, + {"LoadImage", validateLoadImage}, } for _, tc := range tests { tc := tc @@ -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) {