Skip to content

Commit

Permalink
Extend sealer save to support multi-image-archive and tmp-dir
Browse files Browse the repository at this point in the history
feature: Extend the save flag option and function

Signed-off-by: Xinwei Xiong(cubxxw) <[email protected]>
  • Loading branch information
cubxxw committed Apr 21, 2023
1 parent 035b662 commit c5825df
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 54 deletions.
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
46 changes: 33 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,24 @@ 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 +67,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
9 changes: 9 additions & 0 deletions pkg/imageengine/buildah/from.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ type fromFlagsWrapper struct {
*buildahcli.NameSpaceResults
}

type BuildahConfig struct {
ContainersConfDefaultsRO config.Config
}

func NewPodmanConfig() *BuildahConfig {
var buildOptions BuildahConfig
return &buildOptions
}

// createContainerFromImage create a working container. This function is copied from
// "buildah from". This function takes args([]string{"$image"}), and create a working container
// based on $image, this will generate an empty dictionary, not a real rootfs. And this container is a fake container.
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

0 comments on commit c5825df

Please sign in to comment.