Skip to content

Commit

Permalink
feat:support multi arch for load and save (#2062)
Browse files Browse the repository at this point in the history
* feat:support multi arch for load and save

* feat:support multi arch for load and save
  • Loading branch information
kakaZhou719 authored Feb 27, 2023
1 parent af34af6 commit 707f4d1
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 27 deletions.
8 changes: 7 additions & 1 deletion cmd/sealer/cmd/image/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ func NewLoadCmd() *cobra.Command {
if err != nil {
return err
}
return engine.Load(loadOpts)

err = engine.Load(loadOpts)
if err == nil {
logrus.Infof("successfully load %s to image storage", loadOpts.Input)
}

return err
},
}
loadOpts = &options.LoadOptions{}
Expand Down
8 changes: 7 additions & 1 deletion cmd/sealer/cmd/image/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,14 @@ func NewSaveCmd() *cobra.Command {
if err != nil {
return err
}

saveOpts.ImageNameOrID = args[0]
return engine.Save(saveOpts)

err = engine.Save(saveOpts)
if err == nil {
logrus.Infof("successfully save %s to %s", args[0], saveOpts.Output)
}
return err
},
}
saveOpts = &options.SaveOptions{}
Expand Down
122 changes: 115 additions & 7 deletions pkg/imageengine/buildah/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,23 @@ package buildah
import (
"context"
"fmt"

"github.com/sealerio/sealer/pkg/define/options"

"io/fs"
"io/ioutil"
"os"

"path/filepath"
"strings"

"github.com/containers/common/libimage"
"github.com/go-errors/errors"
"github.com/sealerio/sealer/common"
"github.com/sealerio/sealer/pkg/define/options"
"github.com/sealerio/sealer/utils/archive"
fsUtil "github.com/sealerio/sealer/utils/os/fs"
"github.com/sirupsen/logrus"
)

var LoadError = errors.Errorf("failed to load new image")

func (engine *Engine) Load(opts *options.LoadOptions) error {
// Download the input file if needed.
//if strings.HasPrefix(opts.Input, "https://") || strings.HasPrefix(opts.Input, "http://") {
Expand All @@ -42,7 +49,8 @@ func (engine *Engine) Load(opts *options.LoadOptions) error {
// loadOpts.Input = tmpfile
//}

if _, err := os.Stat(opts.Input); err != nil {
imageSrc := opts.Input
if _, err := os.Stat(imageSrc); err != nil {
return err
}

Expand All @@ -51,10 +59,110 @@ func (engine *Engine) Load(opts *options.LoadOptions) error {
loadOpts.Writer = os.Stderr
}

loadedImages, err := engine.ImageRuntime().Load(context.Background(), opts.Input, loadOpts)
srcFile, err := os.Open(filepath.Clean(imageSrc))
if err != nil {
return fmt.Errorf("failed to open %s, err : %v", imageSrc, err)
}

defer func() {
if err := srcFile.Close(); err != nil {
logrus.Errorf("failed to close file: %v", err)
}
}()

tempDir, err := fsUtil.FS.MkTmpdir()
if err != nil {
return fmt.Errorf("failed to create %s, err: %v", tempDir, err)
}

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

// decompress tar file
if _, err = archive.Decompress(srcFile, tempDir, archive.Options{Compress: false}); err != nil {
return err
}

metaFile := filepath.Join(tempDir, common.DefaultMetadataName)
if _, err := os.Stat(metaFile); err != nil {
//assume it is single image to load
return engine.loadOneImage(imageSrc, loadOpts)
}

// get manifestName
metaBytes, err := ioutil.ReadFile(filepath.Clean(metaFile))
if err != nil {
return err
}
fmt.Println("Loaded image: " + strings.Join(loadedImages, "\nLoaded image: "))

manifestName := string(metaBytes)
// delete it if manifestName is already used
_, err = engine.ImageRuntime().LookupManifestList(manifestName)
if err == nil {
logrus.Warnf("%s is already in use, will delete it", manifestName)
delErr := engine.DeleteManifests([]string{manifestName}, &options.ManifestDeleteOpts{})
if delErr != nil {
return fmt.Errorf("%s is already in use: %v", manifestName, delErr)
}
}

// walk through temp dir to load each instance
var instancesIDs []string
err = filepath.Walk(tempDir, func(path string, f fs.FileInfo, err error) error {
if err != nil {
return err
}

if !strings.HasSuffix(f.Name(), ".tar") {
return nil
}

instanceSrc := filepath.Join(tempDir, f.Name())
err = engine.loadOneImage(instanceSrc, loadOpts)
if err != nil {
return fmt.Errorf("failed to load %s from %s: %v", f.Name(), imageSrc, err)
}

instancesIDs = append(instancesIDs, strings.TrimSuffix(f.Name(), ".tar"))
return nil
})

// create a new manifest and add instance to it.
err = engine.CreateManifest(manifestName, &options.ManifestCreateOpts{})
if err != nil {
return fmt.Errorf("failed to create new manifest %s :%v ", manifestName, err)
}

defer func() {
if errors.Is(err, LoadError) {
err = engine.DeleteManifests([]string{manifestName}, &options.ManifestDeleteOpts{})
if err != nil {
logrus.Errorf("failed to delete manifest %s :%v ", manifestName, err)
}
}
}()

for _, imageID := range instancesIDs {
err = engine.AddToManifest(manifestName, imageID, &options.ManifestAddOpts{})
if err != nil {
logrus.Errorf("failed to add new image %s to %s :%v ", imageID, manifestName, err)
return LoadError
}
}

return nil
}

func (engine *Engine) loadOneImage(imageSrc string, loadOpts *libimage.LoadOptions) error {
loadedImages, err := engine.ImageRuntime().Load(context.Background(), imageSrc, loadOpts)
if err != nil {
return err
}

logrus.Infof("Loaded image: " + strings.Join(loadedImages, "\nLoaded image: "))
return nil
}
11 changes: 2 additions & 9 deletions pkg/imageengine/buildah/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,7 @@ func (engine *Engine) CreateManifest(name string, opts *options.ManifestCreateOp
if err != nil {
return fmt.Errorf("encountered while expanding image name %q: %w", name, err)
}

imageID, err := list.SaveToImage(store, "", names, manifest.DockerV2ListMediaType)
if err == nil {
logrus.Infof("%s", imageID)
}
_, err = list.SaveToImage(store, "", names, manifest.DockerV2ListMediaType)

return err
}
Expand Down Expand Up @@ -330,10 +326,7 @@ func (engine *Engine) AddToManifest(name, imageSpec string, opts *options.Manife
}
}

updatedListID, err := list.SaveToImage(store, manifestList.ID(), nil, "")
if err == nil {
logrus.Infof("%s: %s", updatedListID, digestID.String())
}
_, err = list.SaveToImage(store, manifestList.ID(), nil, "")

return err
}
Expand Down
125 changes: 116 additions & 9 deletions pkg/imageengine/buildah/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,141 @@ package buildah

import (
"context"

"github.com/sealerio/sealer/pkg/define/options"
"fmt"
"io"
"os"
"path/filepath"

"github.com/containers/common/libimage"
"github.com/pkg/errors"
"github.com/sealerio/sealer/common"
"github.com/sealerio/sealer/pkg/define/options"
"github.com/sealerio/sealer/utils/archive"
osi "github.com/sealerio/sealer/utils/os"
"github.com/sealerio/sealer/utils/os/fs"
"github.com/sirupsen/logrus"
)

// 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 {
if len(opts.ImageNameOrID) == 0 {
imageNameOrID := opts.ImageNameOrID
imageTar := opts.Output

if len(imageNameOrID) == 0 {
return errors.New("image name or id must be specified")
}
if opts.Compress && (opts.Format != OCIManifestDir && opts.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,
})
if err != nil {
return err
}

isManifest, err := img.IsManifestList(getContext())
if err != nil {
return err
}

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

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

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

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

defer func() {
if err := file.Close(); err != nil {
logrus.Errorf("failed to close file: %v", err)
}
}()

tempDir, err := fs.FS.MkTmpdir()
if err != nil {
return fmt.Errorf("failed to create %s, err: %v", tempDir, err)
}

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

// save each platform images
imageName := img.Names()[0]
logrus.Infof("image %q is a manifest list, looking up matching instance to save", imageNameOrID)
manifestList, err := engine.ImageRuntime().LookupManifestList(imageName)
if err != nil {
return err
}

schema2List, err := manifestList.Inspect()
if err != nil {
return err
}

for _, m := range schema2List.Manifests {
instance, err := manifestList.LookupInstance(engine.Context(), m.Platform.Architecture, m.Platform.OS, m.Platform.Variant)
if err != nil {
return err
}

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

pathsToCompress = append(pathsToCompress, instanceTar)
}

// save imageName to metadata file
metaFile := filepath.Join(tempDir, common.DefaultMetadataName)
if err = osi.NewAtomicWriter(metaFile).WriteFile([]byte(imageName)); err != nil {
return fmt.Errorf("failed to write temp file %s, err: %v ", metaFile, err)
}
pathsToCompress = append(pathsToCompress, metaFile)

// tar all materials
tarReader, err := archive.TarWithRootDir(pathsToCompress...)
if err != nil {
return fmt.Errorf("failed to get tar reader for %s, err: %s", imageNameOrID, err)
}
defer func() {
if err := tarReader.Close(); err != nil {
logrus.Errorf("failed to close file: %v", err)
}
}()

_, err = io.Copy(file, tarReader)

return err
}

func (engine *Engine) saveOneImage(imageNameOrID, format, path string, compress bool) error {
saveOptions := &libimage.SaveOptions{
CopyOptions: libimage.CopyOptions{
DirForceCompress: opts.Compress,
DirForceCompress: compress,
OciAcceptUncompressedLayers: false,
// Force signature removal to preserve backwards compat.
// See https://github.com/containers/podman/pull/11669#issuecomment-925250264
RemoveSignatures: true,
},
}

// TODO we can support multiAchieve in the future
// check podman save
names := []string{opts.ImageNameOrID}

return engine.ImageRuntime().Save(context.Background(), names, opts.Format, opts.Output, saveOptions)
names := []string{imageNameOrID}
return engine.ImageRuntime().Save(context.Background(), names, format, path, saveOptions)
}

0 comments on commit 707f4d1

Please sign in to comment.