github.com/alibaba/sealer@v0.8.6-0.20220430115802-37a2bdaa8173/pkg/image/default_image.go (about) 1 // Copyright © 2021 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 image 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "os" 22 "path/filepath" 23 24 "github.com/distribution/distribution/v3" 25 "github.com/distribution/distribution/v3/manifest/schema2" 26 dockerstreams "github.com/docker/cli/cli/streams" 27 "github.com/docker/docker/api/types" 28 dockerioutils "github.com/docker/docker/pkg/ioutils" 29 dockerjsonmessage "github.com/docker/docker/pkg/jsonmessage" 30 dockerprogress "github.com/docker/docker/pkg/progress" 31 "github.com/docker/docker/pkg/streamformatter" 32 33 "github.com/alibaba/sealer/common" 34 "github.com/alibaba/sealer/logger" 35 "github.com/alibaba/sealer/pkg/image/distributionutil" 36 "github.com/alibaba/sealer/pkg/image/reference" 37 "github.com/alibaba/sealer/pkg/image/store" 38 v1 "github.com/alibaba/sealer/types/api/v1" 39 "github.com/alibaba/sealer/utils" 40 ) 41 42 // DefaultImageService is the default service, which is used for image pull/push 43 type DefaultImageService struct { 44 imageStore store.ImageStore 45 } 46 47 // PullIfNotExist is used to pull image if not exists locally 48 func (d DefaultImageService) PullIfNotExist(imageName string, platforms []*v1.Platform) error { 49 var plats []*v1.Platform 50 for _, plat := range platforms { 51 img, err := d.GetImageByName(imageName, plat) 52 53 if err != nil { 54 return err 55 } 56 // image not found 57 if img == nil { 58 plats = append(plats, plat) 59 } 60 } 61 62 if len(plats) != 0 { 63 return d.Pull(imageName, plats) 64 } 65 66 return nil 67 } 68 69 func (d DefaultImageService) GetImageByName(imageName string, platform *v1.Platform) (*v1.Image, error) { 70 var img *v1.Image 71 img, err := d.imageStore.GetByName(imageName, platform) 72 if err == nil { 73 logger.Debug("image %s already exists", imageName) 74 return img, nil 75 } 76 return nil, nil 77 } 78 79 // Pull always do pull action 80 func (d DefaultImageService) Pull(imageName string, platforms []*v1.Platform) error { 81 named, err := reference.ParseToNamed(imageName) 82 if err != nil { 83 return err 84 } 85 var ( 86 reader, writer = io.Pipe() 87 writeFlusher = dockerioutils.NewWriteFlusher(writer) 88 progressChanOut = streamformatter.NewJSONProgressOutput(writeFlusher, false) 89 streamOut = dockerstreams.NewOut(common.StdOut) 90 ctx = context.Background() 91 ) 92 defer func() { 93 _ = reader.Close() 94 _ = writer.Close() 95 _ = writeFlusher.Close() 96 }() 97 98 layerStore, err := store.NewDefaultLayerStore() 99 if err != nil { 100 return err 101 } 102 103 repo, err := distributionutil.NewV2Repository(named, "pull") 104 if err != nil { 105 return err 106 } 107 108 manifest, err := repo.Manifests(ctx, make([]distribution.ManifestServiceOption, 0)...) 109 if err != nil { 110 return fmt.Errorf("get manifest service error: %v", err) 111 } 112 desc, err := repo.Tags(ctx).Get(ctx, named.Tag()) 113 if err != nil { 114 return fmt.Errorf("get %s tag descriptor error: %v, try \"docker login\" if you are using a private registry", named.Repo(), err) 115 } 116 117 puller, err := distributionutil.NewPuller(repo, distributionutil.Config{ 118 LayerStore: layerStore, 119 ProgressOutput: progressChanOut, 120 }) 121 if err != nil { 122 return err 123 } 124 125 go func() { 126 err := dockerjsonmessage.DisplayJSONMessagesToStream(reader, streamOut, nil) 127 if err != nil && err != io.ErrClosedPipe { 128 logger.Warn("error occurs in display progressing, err: %s", err) 129 } 130 }() 131 132 dockerprogress.Message(progressChanOut, "", fmt.Sprintf("Start to Pull Image %s", named.Raw())) 133 maniList, err := manifest.Get(ctx, desc.Digest, make([]distribution.ManifestServiceOption, 0)...) 134 if err != nil { 135 return fmt.Errorf("get image manifest error: %v", err) 136 } 137 _, p, err := maniList.Payload() 138 if err != nil { 139 return fmt.Errorf("failed to get image manifest list payload: %v", err) 140 } 141 for _, plat := range platforms { 142 m, err := d.handleManifest(ctx, manifest, p, *plat) 143 if err != nil { 144 return fmt.Errorf("get digest error: %v", err) 145 } 146 147 image, err := puller.Pull(ctx, named, m) 148 if err != nil { 149 return err 150 } 151 152 err = d.imageStore.Save(*image) 153 if err != nil { 154 return err 155 } 156 } 157 dockerprogress.Message(progressChanOut, "", fmt.Sprintf("Success to Pull Image %s", named.Raw())) 158 return nil 159 } 160 161 func (d DefaultImageService) handleManifest(ctx context.Context, manifest distribution.ManifestService, payload []byte, platform v1.Platform) (schema2.Manifest, error) { 162 dgest, err := distributionutil.GetImageManifestDigest(payload, platform) 163 if err != nil { 164 return schema2.Manifest{}, fmt.Errorf("get digest from manifest list error: %v", err) 165 } 166 167 m, err := manifest.Get(ctx, dgest, make([]distribution.ManifestServiceOption, 0)...) 168 if err != nil { 169 return schema2.Manifest{}, fmt.Errorf("get image manifest error: %v", err) 170 } 171 172 _, ok := m.(*schema2.DeserializedManifest) 173 if !ok { 174 return schema2.Manifest{}, fmt.Errorf("failed to parse manifest to DeserializedManifest") 175 } 176 return m.(*schema2.DeserializedManifest).Manifest, nil 177 } 178 179 // Push local image to remote registry 180 func (d DefaultImageService) Push(imageName string) error { 181 named, err := reference.ParseToNamed(imageName) 182 if err != nil { 183 return err 184 } 185 var ( 186 reader, writer = io.Pipe() 187 writeFlusher = dockerioutils.NewWriteFlusher(writer) 188 progressChanOut = streamformatter.NewJSONProgressOutput(writeFlusher, false) 189 streamOut = dockerstreams.NewOut(common.StdOut) 190 ) 191 defer func() { 192 _ = reader.Close() 193 _ = writer.Close() 194 _ = writeFlusher.Close() 195 }() 196 197 layerStore, err := store.NewDefaultLayerStore() 198 if err != nil { 199 return err 200 } 201 202 pusher, err := distributionutil.NewPusher(named, 203 distributionutil.Config{ 204 LayerStore: layerStore, 205 ProgressOutput: progressChanOut, 206 }) 207 if err != nil { 208 return err 209 } 210 go func() { 211 err := dockerjsonmessage.DisplayJSONMessagesToStream(reader, streamOut, nil) 212 // reader may be closed in another goroutine 213 // so do not log warn when err == io.ErrClosedPipe 214 if err != nil && err != io.ErrClosedPipe { 215 logger.Warn("error occurs in display progressing, err: %s", err) 216 } 217 }() 218 219 dockerprogress.Message(progressChanOut, "", fmt.Sprintf("Start to Push Image %s", named.Raw())) 220 err = pusher.Push(context.Background(), named) 221 if err == nil { 222 dockerprogress.Message(progressChanOut, "", fmt.Sprintf("Success to Push Image %s", named.CompleteName())) 223 } 224 return err 225 } 226 227 // Login into a registry, for saving auth info in ~/.docker/config.json 228 func (d DefaultImageService) Login(RegistryURL, RegistryUsername, RegistryPasswd string) error { 229 err := distributionutil.Login(context.Background(), &types.AuthConfig{ServerAddress: RegistryURL, Username: RegistryUsername, Password: RegistryPasswd}) 230 if err != nil { 231 return fmt.Errorf("failed to authenticate %s: %v", RegistryURL, err) 232 } 233 if err := utils.SetDockerConfig(RegistryURL, RegistryUsername, RegistryPasswd); err != nil { 234 return err 235 } 236 logger.Info("%s login %s success", RegistryUsername, RegistryURL) 237 return nil 238 } 239 240 // Delete image layer, image meta, and local registry by id or name. 241 // if only image id,will delete related image data. 242 // if image name,and not specify platform,will delete all image data. 243 // if image name and platforms is not nil will delete all the related platform images. 244 func (d DefaultImageService) Delete(imageNameOrID string, platforms []*v1.Platform) error { 245 var ( 246 err error 247 imageStore = d.imageStore 248 isImageID bool 249 deleteImageIDList []string 250 ) 251 252 imageMetadataMap, err := imageStore.GetImageMetadataMap() 253 if err != nil { 254 return err 255 } 256 257 // detect if the input is image id. 258 for _, manifestList := range imageMetadataMap { 259 for _, m := range manifestList.Manifests { 260 if m.ID == imageNameOrID { 261 isImageID = true 262 break 263 } 264 } 265 } 266 267 if isImageID { 268 // delete image by id 269 err = imageStore.DeleteByID(imageNameOrID) 270 if err != nil { 271 return err 272 } 273 deleteImageIDList = append(deleteImageIDList, imageNameOrID) 274 } else { 275 // delete image by name 276 if len(platforms) == 0 { 277 // delete all platforms 278 manifestList, err := imageStore.GetImageManifestList(imageNameOrID) 279 if err != nil { 280 return err 281 } 282 for _, m := range manifestList { 283 deleteImageIDList = append(deleteImageIDList, m.ID) 284 } 285 if err = imageStore.DeleteByName(imageNameOrID, nil); err != nil { 286 return fmt.Errorf("failed to delete image %s, err: %v", imageNameOrID, err) 287 } 288 } else { 289 // delete user specify platform 290 for _, plat := range platforms { 291 img, err := imageStore.GetByName(imageNameOrID, plat) 292 if err != nil { 293 return fmt.Errorf("image %s not found %v", imageNameOrID, err) 294 } 295 296 if err = imageStore.DeleteByName(imageNameOrID, plat); err != nil { 297 return fmt.Errorf("failed to delete image %s, err: %v", imageNameOrID, err) 298 } 299 deleteImageIDList = append(deleteImageIDList, img.Spec.ID) 300 } 301 } 302 } 303 304 // delete image.yaml file which id not in current imageMetadataMap. 305 for _, id := range utils.RemoveDuplicate(deleteImageIDList) { 306 err = store.DeleteImageLocal(id) 307 if err != nil { 308 return err 309 } 310 } 311 312 err = d.deleteLayers() 313 if err != nil { 314 return err 315 } 316 logger.Info("image %s delete success", imageNameOrID) 317 return nil 318 } 319 320 //delete the unused Layer in the `DefaultLayerDir` directory 321 func (d DefaultImageService) deleteLayers() error { 322 var ( 323 //save a path with desired name as value. 324 pruneMap = make(map[string][]string) 325 ) 326 327 allImageLayerIDList, err := d.getAllLayers() 328 if err != nil { 329 return err 330 } 331 332 pruneMap[common.DefaultLayerDir] = allImageLayerIDList 333 pruneMap[filepath.Join(common.DefaultLayerDBRoot, "sha256")] = allImageLayerIDList 334 335 for root, desired := range pruneMap { 336 subset, err := utils.GetDirNameListInDir(root, utils.FilterOptions{ 337 All: true, 338 WithFullPath: false, 339 }) 340 if err != nil { 341 return err 342 } 343 344 trash := utils.RemoveStrSlice(subset, desired) 345 for _, name := range trash { 346 if err := os.RemoveAll(filepath.Join(root, name)); err != nil { 347 return err 348 } 349 _, err := common.StdOut.WriteString(fmt.Sprintf("%s deleted\n", name)) 350 if err != nil { 351 return err 352 } 353 } 354 } 355 356 return nil 357 } 358 359 // getAllLayers return current image id and layers 360 func (d DefaultImageService) getAllLayers() ([]string, error) { 361 imageMetadataMap, err := d.imageStore.GetImageMetadataMap() 362 var allImageLayerDirs []string 363 364 if err != nil { 365 return nil, err 366 } 367 368 for _, imageMetadata := range imageMetadataMap { 369 for _, m := range imageMetadata.Manifests { 370 image, err := d.imageStore.GetByID(m.ID) 371 if err != nil { 372 return nil, err 373 } 374 for _, layer := range image.Spec.Layers { 375 if layer.ID != "" { 376 allImageLayerDirs = append(allImageLayerDirs, layer.ID.Hex()) 377 } 378 } 379 } 380 } 381 return utils.RemoveDuplicate(allImageLayerDirs), err 382 }