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

new image save command #12162

Merged
merged 2 commits into from
Aug 13, 2021
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
74 changes: 74 additions & 0 deletions cmd/minikube/cmd/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,77 @@ var loadImageCmd = &cobra.Command{
},
}

func readFile(w io.Writer, tmp string) error {
r, err := os.Open(tmp)
if err != nil {
return err
}
_, err = io.Copy(w, r)
if err != nil {
return err
}
err = r.Close()
if err != nil {
return err
}
return nil
}

// saveImageCmd represents the image load command
var saveImageCmd = &cobra.Command{
Use: "save IMAGE [ARCHIVE | -]",
Short: "Save a image from minikube",
Long: "Save a image from minikube",
Example: "minikube image save image\nminikube image save image image.tar",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
exit.Message(reason.Usage, "Please provide an image in the container runtime to save from minikube via <minikube image save IMAGE_NAME>")
}
// Save images from container runtime
profile, err := config.LoadProfile(viper.GetString(config.ProfileName))
if err != nil {
exit.Error(reason.Usage, "loading profile", err)
}

if len(args) > 1 {
output = args[1]

if args[1] == "-" {
tmp, err := ioutil.TempFile("", "image.*.tar")
if err != nil {
exit.Error(reason.GuestImageSave, "Failed to get temp", err)
}
tmp.Close()
output = tmp.Name()
}

if err := machine.DoSaveImages([]string{args[0]}, output, []*config.Profile{profile}, ""); err != nil {
exit.Error(reason.GuestImageSave, "Failed to save image", err)
}

if args[1] == "-" {
err := readFile(os.Stdout, output)
if err != nil {
exit.Error(reason.GuestImageSave, "Failed to read temp", err)
}
os.Remove(output)
}
} else {
if err := machine.SaveAndCacheImages([]string{args[0]}, []*config.Profile{profile}); err != nil {
exit.Error(reason.GuestImageSave, "Failed to save image", err)
}
if imgDaemon || imgRemote {
image.UseDaemon(imgDaemon)
image.UseRemote(imgRemote)
err := image.UploadCachedImage(args[0])
if err != nil {
exit.Error(reason.GuestImageSave, "Failed to save image", err)
}
}
}
},
}

var removeImageCmd = &cobra.Command{
Use: "rm IMAGE [IMAGE...]",
Short: "Remove one or more images",
Expand Down Expand Up @@ -258,5 +329,8 @@ func init() {
buildImageCmd.Flags().StringArrayVar(&buildEnv, "build-env", nil, "Environment variables to pass to the build. (format: key=value)")
buildImageCmd.Flags().StringArrayVar(&buildOpt, "build-opt", nil, "Specify arbitrary flags to pass to the build. (format: key=value)")
imageCmd.AddCommand(buildImageCmd)
saveImageCmd.Flags().BoolVar(&imgDaemon, "daemon", false, "Cache image to docker daemon")
saveImageCmd.Flags().BoolVar(&imgRemote, "remote", false, "Cache image to remote registry")
imageCmd.AddCommand(saveImageCmd)
imageCmd.AddCommand(listImageCmd)
}
59 changes: 59 additions & 0 deletions pkg/minikube/assets/vm_assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"io"
"os"
"path"
"strconv"
"time"

"github.com/pkg/errors"
Expand All @@ -37,8 +38,11 @@ const MemorySource = "memory"
// CopyableFile is something that can be copied
type CopyableFile interface {
io.Reader
io.Writer
GetLength() int
SetLength(int)
GetSourcePath() string
GetTargetPath() string

GetTargetDir() string
GetTargetName() string
Expand All @@ -62,6 +66,11 @@ func (b *BaseAsset) GetSourcePath() string {
return b.SourcePath
}

// GetTargetPath returns target path
func (b *BaseAsset) GetTargetPath() string {
return path.Join(b.GetTargetDir(), b.GetTargetName())
}

// GetTargetDir returns target dir
func (b *BaseAsset) GetTargetDir() string {
return b.TargetDir
Expand All @@ -86,6 +95,7 @@ func (b *BaseAsset) GetModTime() (time.Time, error) {
type FileAsset struct {
BaseAsset
reader io.ReadSeeker
writer io.Writer
file *os.File // Optional pointer to close file through FileAsset.Close()
}

Expand Down Expand Up @@ -134,6 +144,14 @@ func (f *FileAsset) GetLength() (flen int) {
return int(fi.Size())
}

// SetLength sets the file length
func (f *FileAsset) SetLength(flen int) {
err := os.Truncate(f.SourcePath, int64(flen))
if err != nil {
klog.Errorf("truncate(%q) failed: %v", f.SourcePath, err)
}
}

// GetModTime returns modification time of the file
func (f *FileAsset) GetModTime() (time.Time, error) {
fi, err := os.Stat(f.SourcePath)
Expand All @@ -152,6 +170,23 @@ func (f *FileAsset) Read(p []byte) (int, error) {
return f.reader.Read(p)
}

// Write writes the asset
func (f *FileAsset) Write(p []byte) (int, error) {
if f.writer == nil {
f.file.Close()
perms, err := strconv.ParseUint(f.Permissions, 8, 32)
if err != nil || perms > 07777 {
return 0, err
}
f.file, err = os.OpenFile(f.SourcePath, os.O_RDWR|os.O_CREATE, os.FileMode(perms))
if err != nil {
return 0, err
}
f.writer = io.Writer(f.file)
}
return f.writer.Write(p)
}

// Seek resets the reader to offset
func (f *FileAsset) Seek(offset int64, whence int) (int64, error) {
return f.reader.Seek(offset, whence)
Expand All @@ -177,11 +212,23 @@ func (m *MemoryAsset) GetLength() int {
return m.length
}

// SetLength returns length
func (m *MemoryAsset) SetLength(len int) {
m.length = len
}

// Read reads the asset
func (m *MemoryAsset) Read(p []byte) (int, error) {
return m.reader.Read(p)
}

// Writer writes the asset
func (m *MemoryAsset) Write(p []byte) (int, error) {
m.length = len(p)
m.reader = bytes.NewReader(p)
return len(p), nil
}

// Seek resets the reader to offset
func (m *MemoryAsset) Seek(offset int64, whence int) (int64, error) {
return m.reader.Seek(offset, whence)
Expand Down Expand Up @@ -298,6 +345,11 @@ func (m *BinAsset) GetLength() int {
return m.length
}

// SetLength sets length
func (m *BinAsset) SetLength(len int) {
m.length = len
}

// Read reads the asset
func (m *BinAsset) Read(p []byte) (int, error) {
if m.GetLength() == 0 {
Expand All @@ -306,6 +358,13 @@ func (m *BinAsset) Read(p []byte) (int, error) {
return m.reader.Read(p)
}

// Write writes the asset
func (m *BinAsset) Write(p []byte) (int, error) {
m.length = len(p)
m.reader = bytes.NewReader(p)
return len(p), nil
}

// Seek resets the reader to offset
func (m *BinAsset) Seek(offset int64, whence int) (int64, error) {
return m.reader.Seek(offset, whence)
Expand Down
3 changes: 3 additions & 0 deletions pkg/minikube/command/command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ type Runner interface {
// Copy is a convenience method that runs a command to copy a file
Copy(assets.CopyableFile) error

// CopyFrom is a convenience method that runs a command to copy a file back
CopyFrom(assets.CopyableFile) error

// Remove is a convenience method that runs a command to remove a file
Remove(assets.CopyableFile) error
}
Expand Down
18 changes: 18 additions & 0 deletions pkg/minikube/command/exec_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,24 @@ func (e *execRunner) Copy(f assets.CopyableFile) error {
return writeFile(dst, f, os.FileMode(perms))
}

// CopyFrom copies a file
func (e *execRunner) CopyFrom(f assets.CopyableFile) error {
src := path.Join(f.GetTargetDir(), f.GetTargetName())

dst := f.GetSourcePath()
klog.Infof("cp: %s --> %s (%d bytes)", src, dst, f.GetLength())
if f.GetLength() == 0 {
klog.Warningf("0 byte asset: %+v", f)
}

perms, err := strconv.ParseInt(f.GetPermissions(), 8, 0)
if err != nil || perms > 07777 {
return errors.Wrapf(err, "error converting permissions %s to integer", f.GetPermissions())
}

return writeFile(dst, f, os.FileMode(perms))
}

// Remove removes a file
func (e *execRunner) Remove(f assets.CopyableFile) error {
dst := filepath.Join(f.GetTargetDir(), f.GetTargetName())
Expand Down
13 changes: 13 additions & 0 deletions pkg/minikube/command/fake_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,19 @@ func (f *FakeCommandRunner) Copy(file assets.CopyableFile) error {
return nil
}

func (f *FakeCommandRunner) CopyFrom(file assets.CopyableFile) error {
v, ok := f.fileMap.Load(file.GetSourcePath())
if !ok {
return fmt.Errorf("not found in map")
}
b := v.(bytes.Buffer)
_, err := io.Copy(file, &b)
if err != nil {
return errors.Wrapf(err, "error writing file: %+v", file)
}
return nil
}

// Remove removes the filename, file contents key value pair from the stored map
func (f *FakeCommandRunner) Remove(file assets.CopyableFile) error {
f.fileMap.Delete(file.GetSourcePath())
Expand Down
17 changes: 17 additions & 0 deletions pkg/minikube/command/kic_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,15 @@ func (k *kicRunner) Copy(f assets.CopyableFile) error {
return k.copy(tf.Name(), dst)
}

// CopyFrom copies a file
func (k *kicRunner) CopyFrom(f assets.CopyableFile) error {
src := f.GetTargetPath()
dst := f.GetSourcePath()

klog.Infof("%s (direct): %s --> %s", k.ociBin, src, dst)
return k.copyFrom(src, dst)
}

// tempDirectory returns the directory to use as the temp directory
// or an empty string if it should use the os default temp directory.
func tempDirectory(isMinikubeSnap bool, isDockerSnap bool) (string, error) {
Expand All @@ -229,6 +238,14 @@ func (k *kicRunner) copy(src string, dst string) error {
return copyToDocker(src, fullDest)
}

func (k *kicRunner) copyFrom(src string, dst string) error {
fullSource := fmt.Sprintf("%s:%s", k.nameOrID, src)
if k.ociBin == oci.Podman {
return copyToPodman(fullSource, dst)
}
return copyToDocker(fullSource, dst)
}

func (k *kicRunner) chmod(dst string, perm string) error {
_, err := k.RunCmd(exec.Command("sudo", "chmod", perm, dst))
return err
Expand Down
82 changes: 82 additions & 0 deletions pkg/minikube/command/ssh_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ limitations under the License.
package command

import (
"bufio"
"bytes"
"fmt"
"io"
"os/exec"
"path"
"strconv"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -373,3 +376,82 @@ func (s *SSHRunner) Copy(f assets.CopyableFile) error {
}
return g.Wait()
}

// CopyFrom copies a file from the remote over SSH.
func (s *SSHRunner) CopyFrom(f assets.CopyableFile) error {
dst := path.Join(path.Join(f.GetTargetDir(), f.GetTargetName()))

sess, err := s.session()
if err != nil {
return errors.Wrap(err, "NewSession")
}
defer func() {
if err := sess.Close(); err != nil {
if err != io.EOF {
klog.Errorf("session close: %v", err)
}
}
}()

cmd := exec.Command("stat", "-c", "%s", dst)
rr, err := s.RunCmd(cmd)
if err != nil {
return fmt.Errorf("%s: %v", cmd, err)
}
length, err := strconv.Atoi(strings.TrimSuffix(rr.Stdout.String(), "\n"))
if err != nil {
return err
}
src := f.GetSourcePath()
klog.Infof("scp %s --> %s (%d bytes)", dst, src, length)
f.SetLength(length)

r, err := sess.StdoutPipe()
if err != nil {
return errors.Wrap(err, "StdoutPipe")
}
w, err := sess.StdinPipe()
if err != nil {
return errors.Wrap(err, "StdinPipe")
}
// The scpcmd below *should not* return until all data is copied and the
// StdinPipe is closed. But let's use errgroup to make it explicit.
var g errgroup.Group
var copied int64

g.Go(func() error {
defer w.Close()
br := bufio.NewReader(r)
fmt.Fprint(w, "\x00")
b, err := br.ReadBytes('\n')
if err != nil {
return errors.Wrap(err, "ReadBytes")
}
if b[0] != 'C' {
return fmt.Errorf("unexpected: %v", b)
}
fmt.Fprint(w, "\x00")

copied = 0
for copied < int64(length) {
n, err := io.CopyN(f, br, int64(length))
if err != nil {
return errors.Wrap(err, "io.CopyN")
}
copied += n
}
fmt.Fprint(w, "\x00")
err = sess.Wait()
if err != nil {
return err
}
return nil
})

scp := fmt.Sprintf("sudo scp -f %s", f.GetTargetPath())
err = sess.Start(scp)
if err != nil {
return fmt.Errorf("%s: %s", scp, err)
}
return g.Wait()
}
Loading