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 }