github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/imageengine/buildah/save.go (about)

     1  // Copyright © 2022 Alibaba Group Holding Ltd.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package buildah
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"path/filepath"
    23  
    24  	"github.com/containers/common/libimage"
    25  	"github.com/containers/common/libimage/manifests"
    26  	"github.com/pkg/errors"
    27  	"github.com/sealerio/sealer/common"
    28  	"github.com/sealerio/sealer/pkg/define/options"
    29  	"github.com/sealerio/sealer/utils/archive"
    30  	osi "github.com/sealerio/sealer/utils/os"
    31  	"github.com/sealerio/sealer/utils/os/fs"
    32  	"github.com/sirupsen/logrus"
    33  )
    34  
    35  // Save image as tar file, if image is multi-arch image, will save all its instances and manifest name as tar file.
    36  func (engine *Engine) Save(opts *options.SaveOptions) error {
    37  	imageNameOrID := opts.ImageNameOrID
    38  	imageTar := opts.Output
    39  	store := engine.ImageStore()
    40  
    41  	if len(imageNameOrID) == 0 {
    42  		return errors.New("image name or id must be specified")
    43  	}
    44  	if opts.Compress && (opts.Format != OCIManifestDir && opts.Format != V2s2ManifestDir) {
    45  		return errors.New("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'")
    46  	}
    47  
    48  	img, _, err := engine.ImageRuntime().LookupImage(imageNameOrID, &libimage.LookupImageOptions{
    49  		ManifestList: true,
    50  	})
    51  	if err != nil {
    52  		return err
    53  	}
    54  
    55  	isManifest, err := img.IsManifestList(getContext())
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	if !isManifest {
    61  		return engine.saveOneImage(imageNameOrID, opts.Format, imageTar, opts.Compress)
    62  	}
    63  
    64  	// save multi-arch images :including each platform images and manifest.
    65  	var pathsToCompress []string
    66  
    67  	if err := fs.FS.MkdirAll(filepath.Dir(imageTar)); err != nil {
    68  		return fmt.Errorf("failed to create %s, err: %v", imageTar, err)
    69  	}
    70  
    71  	file, err := os.Create(filepath.Clean(imageTar))
    72  	if err != nil {
    73  		return fmt.Errorf("failed to create %s, err: %v", imageTar, err)
    74  	}
    75  
    76  	defer func() {
    77  		if err := file.Close(); err != nil {
    78  			logrus.Errorf("failed to close file: %v", err)
    79  		}
    80  	}()
    81  
    82  	tempDir, err := os.MkdirTemp(opts.TmpDir, "sealer-save-tmp")
    83  	if err != nil {
    84  		return fmt.Errorf("failed to create %s, err: %v", tempDir, err)
    85  	}
    86  
    87  	defer func() {
    88  		err = os.RemoveAll(tempDir)
    89  		if err != nil {
    90  			logrus.Warnf("failed to delete %s: %v", tempDir, err)
    91  		}
    92  	}()
    93  
    94  	// save each platform images
    95  	imageName := img.Names()[0]
    96  	logrus.Infof("image %q is a manifest list, looking up matching instance to save", imageNameOrID)
    97  	manifestList, err := engine.ImageRuntime().LookupManifestList(imageName)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	_, list, err := manifests.LoadFromImage(store, manifestList.ID())
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	for _, instanceDigest := range list.Instances() {
   108  		images, err := store.ImagesByDigest(instanceDigest)
   109  		if err != nil {
   110  			return err
   111  		}
   112  
   113  		if len(images) == 0 {
   114  			return fmt.Errorf("no image matched with digest %s", instanceDigest)
   115  		}
   116  
   117  		instance := images[0]
   118  		instanceTar := filepath.Join(tempDir, instance.ID+".tar")
   119  
   120  		// if instance has "Names", use the first one as saved name
   121  		instanceName := instance.ID
   122  		if len(instance.Names) > 0 {
   123  			instanceName = instance.Names[0]
   124  		}
   125  
   126  		err = engine.saveOneImage(instanceName, opts.Format, instanceTar, opts.Compress)
   127  		if err != nil {
   128  			return err
   129  		}
   130  
   131  		pathsToCompress = append(pathsToCompress, instanceTar)
   132  	}
   133  
   134  	// save imageName to metadata file
   135  	metaFile := filepath.Join(tempDir, common.DefaultMetadataName)
   136  	if err = osi.NewAtomicWriter(metaFile).WriteFile([]byte(imageName)); err != nil {
   137  		return fmt.Errorf("failed to write temp file %s, err: %v ", metaFile, err)
   138  	}
   139  	pathsToCompress = append(pathsToCompress, metaFile)
   140  
   141  	// tar all materials
   142  	tarReader, err := archive.TarWithRootDir(pathsToCompress...)
   143  	if err != nil {
   144  		return fmt.Errorf("failed to get tar reader for %s, err: %s", imageNameOrID, err)
   145  	}
   146  	defer func() {
   147  		if err := tarReader.Close(); err != nil {
   148  			logrus.Errorf("failed to close file: %v", err)
   149  		}
   150  	}()
   151  
   152  	_, err = io.Copy(file, tarReader)
   153  
   154  	return err
   155  }
   156  
   157  func (engine *Engine) saveOneImage(imageNameOrID, format, path string, compress bool) error {
   158  	saveOptions := &libimage.SaveOptions{
   159  		CopyOptions: libimage.CopyOptions{
   160  			DirForceCompress:            compress,
   161  			OciAcceptUncompressedLayers: false,
   162  			// Force signature removal to preserve backwards compat.
   163  			// See https://github.com/containers/podman/pull/11669#issuecomment-925250264
   164  			RemoveSignatures: true,
   165  		},
   166  	}
   167  
   168  	names := []string{imageNameOrID}
   169  	return engine.ImageRuntime().Save(context.Background(), names, format, path, saveOptions)
   170  }