diff --git a/cmd/sealer/cmd/image/load.go b/cmd/sealer/cmd/image/load.go index c8c573a2941..c6268992aa0 100644 --- a/cmd/sealer/cmd/image/load.go +++ b/cmd/sealer/cmd/image/load.go @@ -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 { @@ -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) diff --git a/cmd/sealer/cmd/image/save.go b/cmd/sealer/cmd/image/save.go index f8079d062bd..60885ebec2d 100644 --- a/cmd/sealer/cmd/image/save.go +++ b/cmd/sealer/cmd/image/save.go @@ -15,8 +15,6 @@ package image import ( - "os" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -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 { @@ -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 diff --git a/pkg/define/options/options.go b/pkg/define/options/options.go index 36f3c47a709..749cfd542cd 100644 --- a/pkg/define/options/options.go +++ b/pkg/define/options/options.go @@ -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 { diff --git a/pkg/image/save/save.go b/pkg/image/save/save.go index 2f84f301c75..f599b90fc5d 100644 --- a/pkg/image/save/save.go +++ b/pkg/image/save/save.go @@ -71,14 +71,14 @@ 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 } @@ -86,7 +86,7 @@ func (is *DefaultImageSaver) SaveImages(images []string, dir string, platform v1 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 { diff --git a/pkg/imageengine/buildah/from.go b/pkg/imageengine/buildah/from.go index 52a360bb065..4bdfbcb2eff 100644 --- a/pkg/imageengine/buildah/from.go +++ b/pkg/imageengine/buildah/from.go @@ -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. diff --git a/pkg/imageengine/buildah/load.go b/pkg/imageengine/buildah/load.go index 91bb9674b4a..cb5521192f6 100644 --- a/pkg/imageengine/buildah/load.go +++ b/pkg/imageengine/buildah/load.go @@ -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) } }() diff --git a/pkg/imageengine/buildah/save.go b/pkg/imageengine/buildah/save.go index 7e6411b7f42..ac68f61e057 100644 --- a/pkg/imageengine/buildah/save.go +++ b/pkg/imageengine/buildah/save.go @@ -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" @@ -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() { @@ -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) } }() @@ -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 }