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

feature: Extend sealer save to support multi-image-archive and tmp-dir #2193

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
14 changes: 9 additions & 5 deletions cmd/sealer/cmd/image/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ import (
"github.com/sealerio/sealer/pkg/imageengine"
)

var loadOpts *options.LoadOptions
var (
loadOpts *options.LoadOptions

var longNewLoadCmdDescription = `Load a sealer image from a tar archive`
longNewLoadCmdDescription = `
Load a sealer image from a tar archive
Save an image to docker-archive or oci-archive on the local machine. Default is docker-archive.`

var exampleForLoadCmd = `
exampleForLoadCmd = `
sealer load -i kubernetes.tar
`
sealer save abc:v1 -o my.tar --tmp-dir /root/my-tmp`
)

// NewLoadCmd loadCmd represents the load command
func NewLoadCmd() *cobra.Command {
Expand Down Expand Up @@ -58,7 +62,7 @@ func NewLoadCmd() *cobra.Command {
flags := loadCmd.Flags()
flags.StringVarP(&loadOpts.Input, "input", "i", "", "Load image from file")
flags.BoolVarP(&loadOpts.Quiet, "quiet", "q", false, "Suppress the output")
flags.StringVar(&loadOpts.TmpDir, "tmp-dir", "", "set temporary directory when load image. if not set, use system`s temporary directory")
flags.StringVar(&loadOpts.TmpDir, "tmp-dir", "", "Set temporary directory when load image. if not set, use system temporary directory(`/var/tmp/`)")
if err := loadCmd.MarkFlagRequired("input"); err != nil {
logrus.Errorf("failed to init flag: %v", err)
os.Exit(1)
Expand Down
44 changes: 31 additions & 13 deletions cmd/sealer/cmd/image/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
package image

import (
"os"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"

Expand All @@ -25,14 +23,22 @@ import (
"github.com/sealerio/sealer/pkg/imageengine/buildah"
)

var saveOpts *options.SaveOptions
var (
saveOpts *options.SaveOptions

longNewSaveCmdDescription = `
sealer save -o [output file name] [image name]
Save an image to docker-archive or oci-archive on the local machine. Default is docker-archive.`

exampleForSaveCmd = `
sealer save docker.io/sealerio/kubernetes:v1-22-15-sealerio-2

var longNewSaveCmdDescription = `sealer save -o [output file name] [image name]`
Image to kubernetes.tar file, and specify the temporary load directory:

var exampleForSaveCmd = `
save docker.io/sealerio/kubernetes:v1-22-15-sealerio-2 image to kubernetes.tar file:
sealer save docker.io/sealerio/kubernetes:v1.22.15 -o kubernetes.tar --tmp-dir /root/tmp`
)

sealer save -o kubernetes.tar docker.io/sealerio/kubernetes:v1-22-15-sealerio-2`
// var containerConfig = buildah.NewPodmanConfig()

// NewSaveCmd saveCmd represents the save command
func NewSaveCmd() *cobra.Command {
Expand All @@ -59,14 +65,26 @@ func NewSaveCmd() *cobra.Command {
}
saveOpts = &options.SaveOptions{}
flags := saveCmd.Flags()
flags.StringVar(&saveOpts.Format, "format", buildah.OCIArchive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)")
flags.StringVarP(&saveOpts.Output, "output", "o", "", "Write image to a specified file")

formatFlagName := "format"
flags.StringVar(&saveOpts.Format, formatFlagName, buildah.OCIArchive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)")

outputFlagName := "output"
flags.StringVarP(&saveOpts.Output, outputFlagName, "o", "", "Write to a specified file (default: stdout, which must be redirected)")

// TODO: Waiting for implementation, not yet supported
flags.StringVar(&loadOpts.TmpDir, "tmp-dir", "", "Set temporary directory when load image. use system temporary directory(/var/tmp/) if not present.")

flags.BoolVarP(&saveOpts.Quiet, "quiet", "q", false, "Suppress the output")
flags.StringVar(&saveOpts.TmpDir, "tmp-dir", "", "set temporary directory when save image. if not set, use system`s temporary directory")
flags.BoolVar(&saveOpts.Compress, "compress", false, "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)")

compressFlagName := "compress"
flags.BoolVar(&saveOpts.Compress, compressFlagName, false, "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)")

// MultiImageArchiveFlagName := "multi-image-archive"
// flags.BoolVarP(&saveOpts.MultiImageArchive, MultiImageArchiveFlagName, "m", containerConfig.ContainersConfDefaultsRO.Engine.MultiImageArchive, "Interpret additional arguments as images not tags and create a multi-image-archive (only for docker-archive)")

if err := saveCmd.MarkFlagRequired("output"); err != nil {
logrus.Errorf("failed to init flag: %v", err)
os.Exit(1)
logrus.WithError(err).Fatal("failed to mark flag as required")
}

return saveCmd
Expand Down
17 changes: 9 additions & 8 deletions pkg/define/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,16 @@ type ImagesOptions struct {
JSON bool
}

// SaveOptions provide options for saving images.
type SaveOptions struct {
Compress bool
Format string
// don't support currently
MultiImageArchive bool
Output string
Quiet bool
ImageNameOrID string
TmpDir string
Compress bool // Whether or not to compress the image layers when saving to a directory. Default is false.
Format string // The format to save the image in. Possible values are oci-archive, oci-dir, docker-archive, and docker-dir. Default is oci-archive.
MultiImageArchive bool // Whether or not to save multiple images into a single archive file. Default is false.
Output string // The file or directory to save the image to. If not set, output will go to stdout.
Quiet bool // Whether or not to suppress output when saving the image. Default is false.
ImageNameOrID string // The name or ID of the image to save.
// // TODO: unrealized, Set temporary directory when save image. if not set, use system temporary directory(`/var/tmp/`)
TmpDir string
}

type LoadOptions struct {
Expand Down
6 changes: 3 additions & 3 deletions pkg/image/save/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,22 +71,22 @@ func (is *DefaultImageSaver) SaveImages(images []string, dir string, platform v1
}
}()

//handle image name
// handle image name
for _, image := range images {
named, err := ParseNormalizedNamed(image, "")
if err != nil {
return fmt.Errorf("failed to parse image name:: %v", err)
}

//check if image exist
// check if image exist
if err := is.isImageExist(named, dir, platform); err == nil {
continue
}
is.domainToImages[named.domain+named.repo] = append(is.domainToImages[named.domain+named.repo], named)
progress.Message(is.progressOut, "", fmt.Sprintf("Pulling image: %s", named.FullName()))
}

//perform image save ability
// perform image save ability
eg, _ := errgroup.WithContext(context.Background())
numCh := make(chan struct{}, maxPullGoroutineNum)
for _, nameds := range is.domainToImages {
Expand Down
5 changes: 2 additions & 3 deletions pkg/imageengine/buildah/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,8 @@ func (engine *Engine) Load(opts *options.LoadOptions) error {
}

defer func() {
err = os.RemoveAll(tempDir)
if err != nil {
logrus.Errorf("failed to delete %s: %v", tempDir, err)
if err = os.RemoveAll(tempDir); err != nil {
logrus.Warnf("failed to delete %s: %v", tempDir, err)
}
}()

Expand Down
56 changes: 34 additions & 22 deletions pkg/imageengine/buildah/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"path/filepath"

"github.com/containers/common/libimage"
"github.com/containers/common/pkg/auth"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"

Expand All @@ -34,42 +35,54 @@ import (

// Save image as tar file, if image is multi-arch image, will save all its instances and manifest name as tar file.
func (engine *Engine) Save(opts *options.SaveOptions) error {
imageNameOrID := opts.ImageNameOrID
imageTar := opts.Output
var (
imageNameOrID = opts.ImageNameOrID
output = opts.Output
format = opts.Format
tmpDir = opts.TmpDir
compress = opts.Compress
)

systemCxt := engine.SystemContext()
if err := auth.CheckAuthFile(systemCxt.AuthFilePath); err != nil {
return err
}

systemCxt.BigFilesTemporaryDir = tmpDir

if len(imageNameOrID) == 0 {
return errors.New("image name or id must be specified")
return errors.New("failed to save image, image name or id is empty")
}
if opts.Compress && (opts.Format != OCIManifestDir && opts.Format != V2s2ManifestDir) {

if compress && (format != OCIManifestDir && format != V2s2ManifestDir) {
return errors.New("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'")
}

img, _, err := engine.ImageRuntime().LookupImage(imageNameOrID, &libimage.LookupImageOptions{
ManifestList: true,
})
img, _, err := engine.ImageRuntime().LookupImage(imageNameOrID,
&libimage.LookupImageOptions{
ManifestList: true,
})
if err != nil {
return err
}

isManifest, err := img.IsManifestList(getContext())
if err != nil {
// checks if the image is a manifest list or an image index, and saves the image if it is not
if isManifest, err := img.IsManifestList(getContext()); err != nil {
return err
}

if !isManifest {
return engine.saveOneImage(imageNameOrID, opts.Format, imageTar, opts.Compress)
} else if !isManifest {
return engine.saveOneImage(imageNameOrID, format, output, compress)
}

// save multi-arch images :including each platform images and manifest.
var pathsToCompress []string
pathsToCompress := []string{}

if err := fs.FS.MkdirAll(filepath.Dir(imageTar)); err != nil {
return fmt.Errorf("failed to create %s, err: %v", imageTar, err)
if err := fs.FS.MkdirAll(filepath.Dir(output)); err != nil {
return fmt.Errorf("failed to create %s, err: %v", output, err)
}

file, err := os.Create(filepath.Clean(imageTar))
file, err := os.Create(filepath.Clean(output))
if err != nil {
return fmt.Errorf("failed to create %s, err: %v", imageTar, err)
return fmt.Errorf("failed to create %s, err: %v", output, err)
}

defer func() {
Expand All @@ -78,14 +91,13 @@ func (engine *Engine) Save(opts *options.SaveOptions) error {
}
}()

tempDir, err := os.MkdirTemp(opts.TmpDir, "sealer-save-tmp")
tempDir, err := os.MkdirTemp(tmpDir, "sealer-save-tmp")
if err != nil {
return fmt.Errorf("failed to create %s, err: %v", tempDir, err)
}

defer func() {
err = os.RemoveAll(tempDir)
if err != nil {
if err = os.RemoveAll(tempDir); err != nil {
logrus.Warnf("failed to delete %s: %v", tempDir, err)
}
}()
Expand All @@ -110,7 +122,7 @@ func (engine *Engine) Save(opts *options.SaveOptions) error {
}

instanceTar := filepath.Join(tempDir, instance.ID()+".tar")
err = engine.saveOneImage(instance.ID(), opts.Format, instanceTar, opts.Compress)
err = engine.saveOneImage(instance.ID(), format, instanceTar, compress)
if err != nil {
return err
}
Expand Down