Merge pull request #10807 from afbjorklund/load-new
Image load: Allow loading local images from tar or cachepull/10922/head
commit
77c6de357d
|
@ -17,10 +17,16 @@ limitations under the License.
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"k8s.io/minikube/pkg/minikube/config"
|
"k8s.io/minikube/pkg/minikube/config"
|
||||||
"k8s.io/minikube/pkg/minikube/exit"
|
"k8s.io/minikube/pkg/minikube/exit"
|
||||||
|
"k8s.io/minikube/pkg/minikube/image"
|
||||||
"k8s.io/minikube/pkg/minikube/machine"
|
"k8s.io/minikube/pkg/minikube/machine"
|
||||||
"k8s.io/minikube/pkg/minikube/reason"
|
"k8s.io/minikube/pkg/minikube/reason"
|
||||||
)
|
)
|
||||||
|
@ -32,11 +38,33 @@ var imageCmd = &cobra.Command{
|
||||||
Long: "Load a local image into minikube",
|
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
|
// loadImageCmd represents the image load command
|
||||||
var loadImageCmd = &cobra.Command{
|
var loadImageCmd = &cobra.Command{
|
||||||
Use: "load",
|
Use: "load IMAGE | ARCHIVE | -",
|
||||||
Short: "Load a local image into minikube",
|
Short: "Load a image into minikube",
|
||||||
Long: "Load a local 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) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if len(args) == 0 {
|
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>")
|
exit.Message(reason.Usage, "Please provide an image in your local daemon to load into minikube via <minikube image load IMAGE_NAME>")
|
||||||
|
@ -46,13 +74,59 @@ var loadImageCmd = &cobra.Command{
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exit.Error(reason.Usage, "loading profile", err)
|
exit.Error(reason.Usage, "loading profile", err)
|
||||||
}
|
}
|
||||||
img := args[0]
|
|
||||||
if err := machine.CacheAndLoadImages([]string{img}, []*config.Profile{profile}); err != nil {
|
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)
|
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() {
|
func init() {
|
||||||
imageCmd.AddCommand(loadImageCmd)
|
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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -859,7 +859,7 @@ func (k *Bootstrapper) UpdateCluster(cfg config.ClusterConfig) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.KubernetesConfig.ShouldLoadCachedImages {
|
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})
|
out.FailureT("Unable to load cached images: {{.error}}", out.V{"error": err})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,21 @@ var defaultPlatform = v1.Platform{
|
||||||
OS: "linux",
|
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
|
// DigestByDockerLib uses client by docker lib to return image digest
|
||||||
// img.ID in as same as image digest
|
// img.ID in as same as image digest
|
||||||
func DigestByDockerLib(imgClient *client.Client, imgName string) string {
|
func DigestByDockerLib(imgClient *client.Client, imgName string) string {
|
||||||
|
@ -198,7 +213,31 @@ func WriteImageToDaemon(img string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func retrieveImage(ref name.Reference) (v1.Image, 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)
|
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)
|
img, err := daemon.Image(ref)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
klog.Infof("found %s locally: %+v", ref.Name(), img)
|
klog.Infof("found %s locally: %+v", ref.Name(), img)
|
||||||
|
@ -206,12 +245,7 @@ func retrieveImage(ref name.Reference) (v1.Image, error) {
|
||||||
}
|
}
|
||||||
// reference does not exist in the local daemon
|
// reference does not exist in the local daemon
|
||||||
klog.Infof("daemon lookup for %+v: %v", ref, err)
|
klog.Infof("daemon lookup for %+v: %v", ref, err)
|
||||||
|
return img, err
|
||||||
img, err = retrieveRemote(ref, defaultPlatform)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return fixPlatform(ref, img, defaultPlatform)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func retrieveRemote(ref name.Reference, p v1.Platform) (v1.Image, error) {
|
func retrieveRemote(ref name.Reference, p v1.Platform) (v1.Image, error) {
|
||||||
|
@ -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)
|
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
|
// See https://github.com/kubernetes/minikube/issues/10402
|
||||||
|
|
|
@ -61,8 +61,8 @@ func CacheImagesForBootstrapper(imageRepository string, version string, clusterB
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadImages loads previously cached images into the container runtime
|
// LoadCachedImages loads previously cached images into the container runtime
|
||||||
func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string, cacheDir string) error {
|
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})
|
cr, err := cruntime.New(cruntime.Config{Type: cc.KubernetesConfig.ContainerRuntime, Runner: runner})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "runtime")
|
return errors.Wrap(err, "runtime")
|
||||||
|
@ -73,6 +73,7 @@ func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string
|
||||||
klog.Infof("Images are preloaded, skipping loading")
|
klog.Infof("Images are preloaded, skipping loading")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
klog.Infof("LoadImages start: %s", images)
|
klog.Infof("LoadImages start: %s", images)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
@ -102,7 +103,7 @@ func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
klog.Infof("%q needs transfer: %v", image, err)
|
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 {
|
if err := g.Wait(); err != nil {
|
||||||
|
@ -157,6 +158,22 @@ func needsTransfer(imgClient *client.Client, imgName string, cr cruntime.Manager
|
||||||
return nil
|
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
|
// CacheAndLoadImages caches and loads images to all profiles
|
||||||
func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
|
func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
|
||||||
if len(images) == 0 {
|
if len(images) == 0 {
|
||||||
|
@ -168,6 +185,11 @@ func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
|
||||||
return errors.Wrap(err, "save to dir")
|
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()
|
api, err := NewAPIClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "api")
|
return errors.Wrap(err, "api")
|
||||||
|
@ -209,7 +231,13 @@ func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
failed = append(failed, m)
|
failed = append(failed, m)
|
||||||
klog.Warningf("Failed to load cached images for profile %s. make sure the profile is running. %v", pName, err)
|
klog.Warningf("Failed to load cached images for profile %s. make sure the profile is running. %v", pName, err)
|
||||||
|
@ -226,15 +254,20 @@ func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// transferAndLoadImage transfers and loads a single image from the cache
|
// transferAndLoadCachedImage transfers and loads a single image from the cache
|
||||||
func transferAndLoadImage(cr command.Runner, k8s config.KubernetesConfig, imgName string, cacheDir string) error {
|
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})
|
r, err := cruntime.New(cruntime.Config{Type: k8s.ContainerRuntime, Runner: cr})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "runtime")
|
return errors.Wrap(err, "runtime")
|
||||||
}
|
}
|
||||||
src := filepath.Join(cacheDir, imgName)
|
klog.Infof("Loading image from: %s", src)
|
||||||
src = localpath.SanitizeCacheDir(src)
|
|
||||||
klog.Infof("Loading image from cache: %s", src)
|
|
||||||
filename := filepath.Base(src)
|
filename := filepath.Base(src)
|
||||||
if _, err := os.Stat(src); err != nil {
|
if _, err := os.Stat(src); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -72,14 +72,28 @@ minikube image help [command] [flags]
|
||||||
|
|
||||||
## minikube image load
|
## minikube image load
|
||||||
|
|
||||||
Load a local image into minikube
|
Load a image into minikube
|
||||||
|
|
||||||
### Synopsis
|
### Synopsis
|
||||||
|
|
||||||
Load a local image into minikube
|
Load a image into minikube
|
||||||
|
|
||||||
```shell
|
```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
|
### Options inherited from parent commands
|
||||||
|
|
Loading…
Reference in New Issue