github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+incompatible/image/tarexport/save.go (about) 1 package tarexport // import "github.com/docker/docker/image/tarexport" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "path" 10 "path/filepath" 11 "time" 12 13 "github.com/containerd/containerd/images" 14 "github.com/containerd/log" 15 "github.com/distribution/reference" 16 "github.com/docker/distribution" 17 "github.com/docker/docker/api/types/events" 18 "github.com/docker/docker/image" 19 v1 "github.com/docker/docker/image/v1" 20 "github.com/docker/docker/layer" 21 "github.com/docker/docker/pkg/archive" 22 "github.com/docker/docker/pkg/system" 23 "github.com/moby/sys/sequential" 24 "github.com/opencontainers/go-digest" 25 "github.com/opencontainers/image-spec/specs-go" 26 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 27 "github.com/pkg/errors" 28 ) 29 30 type imageDescriptor struct { 31 refs []reference.NamedTagged 32 layers []layer.DiffID 33 image *image.Image 34 layerRef layer.Layer 35 } 36 37 type saveSession struct { 38 *tarexporter 39 outDir string 40 images map[image.ID]*imageDescriptor 41 savedLayers map[layer.DiffID]distribution.Descriptor 42 savedConfigs map[string]struct{} 43 } 44 45 func (l *tarexporter) Save(names []string, outStream io.Writer) error { 46 images, err := l.parseNames(names) 47 if err != nil { 48 return err 49 } 50 51 // Release all the image top layer references 52 defer l.releaseLayerReferences(images) 53 return (&saveSession{tarexporter: l, images: images}).save(outStream) 54 } 55 56 // parseNames will parse the image names to a map which contains image.ID to *imageDescriptor. 57 // Each imageDescriptor holds an image top layer reference named 'layerRef'. It is taken here, should be released later. 58 func (l *tarexporter) parseNames(names []string) (desc map[image.ID]*imageDescriptor, rErr error) { 59 imgDescr := make(map[image.ID]*imageDescriptor) 60 defer func() { 61 if rErr != nil { 62 l.releaseLayerReferences(imgDescr) 63 } 64 }() 65 66 addAssoc := func(id image.ID, ref reference.Named) error { 67 if _, ok := imgDescr[id]; !ok { 68 descr := &imageDescriptor{} 69 if err := l.takeLayerReference(id, descr); err != nil { 70 return err 71 } 72 imgDescr[id] = descr 73 } 74 75 if ref != nil { 76 if _, ok := ref.(reference.Canonical); ok { 77 return nil 78 } 79 tagged, ok := reference.TagNameOnly(ref).(reference.NamedTagged) 80 if !ok { 81 return nil 82 } 83 84 for _, t := range imgDescr[id].refs { 85 if tagged.String() == t.String() { 86 return nil 87 } 88 } 89 imgDescr[id].refs = append(imgDescr[id].refs, tagged) 90 } 91 return nil 92 } 93 94 for _, name := range names { 95 ref, err := reference.ParseAnyReference(name) 96 if err != nil { 97 return nil, err 98 } 99 namedRef, ok := ref.(reference.Named) 100 if !ok { 101 // Check if digest ID reference 102 if digested, ok := ref.(reference.Digested); ok { 103 if err := addAssoc(image.ID(digested.Digest()), nil); err != nil { 104 return nil, err 105 } 106 continue 107 } 108 return nil, errors.Errorf("invalid reference: %v", name) 109 } 110 111 if reference.FamiliarName(namedRef) == string(digest.Canonical) { 112 imgID, err := l.is.Search(name) 113 if err != nil { 114 return nil, err 115 } 116 if err := addAssoc(imgID, nil); err != nil { 117 return nil, err 118 } 119 continue 120 } 121 if reference.IsNameOnly(namedRef) { 122 assocs := l.rs.ReferencesByName(namedRef) 123 for _, assoc := range assocs { 124 if err := addAssoc(image.ID(assoc.ID), assoc.Ref); err != nil { 125 return nil, err 126 } 127 } 128 if len(assocs) == 0 { 129 imgID, err := l.is.Search(name) 130 if err != nil { 131 return nil, err 132 } 133 if err := addAssoc(imgID, nil); err != nil { 134 return nil, err 135 } 136 } 137 continue 138 } 139 id, err := l.rs.Get(namedRef) 140 if err != nil { 141 return nil, err 142 } 143 if err := addAssoc(image.ID(id), namedRef); err != nil { 144 return nil, err 145 } 146 } 147 return imgDescr, nil 148 } 149 150 // takeLayerReference will take/Get the image top layer reference 151 func (l *tarexporter) takeLayerReference(id image.ID, imgDescr *imageDescriptor) error { 152 img, err := l.is.Get(id) 153 if err != nil { 154 return err 155 } 156 if err := image.CheckOS(img.OperatingSystem()); err != nil { 157 return fmt.Errorf("os %q is not supported", img.OperatingSystem()) 158 } 159 imgDescr.image = img 160 topLayerID := img.RootFS.ChainID() 161 if topLayerID == "" { 162 return nil 163 } 164 layer, err := l.lss.Get(topLayerID) 165 if err != nil { 166 return err 167 } 168 imgDescr.layerRef = layer 169 return nil 170 } 171 172 // releaseLayerReferences will release all the image top layer references 173 func (l *tarexporter) releaseLayerReferences(imgDescr map[image.ID]*imageDescriptor) error { 174 for _, descr := range imgDescr { 175 if descr.layerRef != nil { 176 l.lss.Release(descr.layerRef) 177 } 178 } 179 return nil 180 } 181 182 func (s *saveSession) save(outStream io.Writer) error { 183 s.savedConfigs = make(map[string]struct{}) 184 s.savedLayers = make(map[layer.DiffID]distribution.Descriptor) 185 186 // get image json 187 tempDir, err := os.MkdirTemp("", "docker-export-") 188 if err != nil { 189 return err 190 } 191 defer os.RemoveAll(tempDir) 192 193 s.outDir = tempDir 194 reposLegacy := make(map[string]map[string]string) 195 196 var manifest []manifestItem 197 var parentLinks []parentLink 198 199 var manifestDescriptors []ocispec.Descriptor 200 201 for id, imageDescr := range s.images { 202 foreignSrcs, err := s.saveImage(id) 203 if err != nil { 204 return err 205 } 206 207 var ( 208 repoTags []string 209 layers []string 210 foreign = make([]ocispec.Descriptor, 0, len(foreignSrcs)) 211 ) 212 213 // Layers in manifest must follow the actual layer order from config. 214 for _, l := range imageDescr.layers { 215 desc := foreignSrcs[l] 216 foreign = append(foreign, ocispec.Descriptor{ 217 MediaType: desc.MediaType, 218 Digest: desc.Digest, 219 Size: desc.Size, 220 URLs: desc.URLs, 221 Annotations: desc.Annotations, 222 Platform: desc.Platform, 223 }) 224 } 225 226 imgPlat := imageDescr.image.Platform() 227 228 m := ocispec.Manifest{ 229 Versioned: specs.Versioned{ 230 SchemaVersion: 2, 231 }, 232 MediaType: ocispec.MediaTypeImageManifest, 233 Config: ocispec.Descriptor{ 234 MediaType: ocispec.MediaTypeImageConfig, 235 Digest: digest.Digest(imageDescr.image.ID()), 236 Size: int64(len(imageDescr.image.RawJSON())), 237 Platform: &imgPlat, 238 }, 239 Layers: foreign, 240 } 241 242 data, err := json.Marshal(m) 243 if err != nil { 244 return errors.Wrap(err, "error marshaling manifest") 245 } 246 dgst := digest.FromBytes(data) 247 248 mFile := filepath.Join(s.outDir, ocispec.ImageBlobsDir, dgst.Algorithm().String(), dgst.Encoded()) 249 if err := os.MkdirAll(filepath.Dir(mFile), 0o755); err != nil { 250 return errors.Wrap(err, "error creating blob directory") 251 } 252 if err := system.Chtimes(filepath.Dir(mFile), time.Unix(0, 0), time.Unix(0, 0)); err != nil { 253 return errors.Wrap(err, "error setting blob directory timestamps") 254 } 255 if err := os.WriteFile(mFile, data, 0o644); err != nil { 256 return errors.Wrap(err, "error writing oci manifest file") 257 } 258 if err := system.Chtimes(mFile, time.Unix(0, 0), time.Unix(0, 0)); err != nil { 259 return errors.Wrap(err, "error setting blob directory timestamps") 260 } 261 size := int64(len(data)) 262 263 untaggedMfstDesc := ocispec.Descriptor{ 264 MediaType: ocispec.MediaTypeImageManifest, 265 Digest: dgst, 266 Size: size, 267 Platform: m.Config.Platform, 268 } 269 for _, ref := range imageDescr.refs { 270 familiarName := reference.FamiliarName(ref) 271 if _, ok := reposLegacy[familiarName]; !ok { 272 reposLegacy[familiarName] = make(map[string]string) 273 } 274 reposLegacy[familiarName][ref.Tag()] = digest.Digest(imageDescr.layers[len(imageDescr.layers)-1]).Encoded() 275 repoTags = append(repoTags, reference.FamiliarString(ref)) 276 277 taggedManifest := untaggedMfstDesc 278 taggedManifest.Annotations = map[string]string{ 279 images.AnnotationImageName: ref.String(), 280 ocispec.AnnotationRefName: ref.Tag(), 281 } 282 manifestDescriptors = append(manifestDescriptors, taggedManifest) 283 } 284 285 // If no ref was assigned, make sure still add the image is still included in index.json. 286 if len(manifestDescriptors) == 0 { 287 manifestDescriptors = append(manifestDescriptors, untaggedMfstDesc) 288 } 289 290 for _, l := range imageDescr.layers { 291 // IMPORTANT: We use path, not filepath here to ensure the layers 292 // in the manifest use Unix-style forward-slashes. 293 lDgst := digest.Digest(l) 294 layers = append(layers, path.Join(ocispec.ImageBlobsDir, lDgst.Algorithm().String(), lDgst.Encoded())) 295 } 296 297 manifest = append(manifest, manifestItem{ 298 Config: path.Join(ocispec.ImageBlobsDir, id.Digest().Algorithm().String(), id.Digest().Encoded()), 299 RepoTags: repoTags, 300 Layers: layers, 301 LayerSources: foreignSrcs, 302 }) 303 304 parentID, _ := s.is.GetParent(id) 305 parentLinks = append(parentLinks, parentLink{id, parentID}) 306 s.tarexporter.loggerImgEvent.LogImageEvent(id.String(), id.String(), events.ActionSave) 307 } 308 309 for i, p := range validatedParentLinks(parentLinks) { 310 if p.parentID != "" { 311 manifest[i].Parent = p.parentID 312 } 313 } 314 315 if len(reposLegacy) > 0 { 316 reposFile := filepath.Join(tempDir, legacyRepositoriesFileName) 317 rf, err := os.OpenFile(reposFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644) 318 if err != nil { 319 return err 320 } 321 322 if err := json.NewEncoder(rf).Encode(reposLegacy); err != nil { 323 rf.Close() 324 return err 325 } 326 327 rf.Close() 328 329 if err := system.Chtimes(reposFile, time.Unix(0, 0), time.Unix(0, 0)); err != nil { 330 return err 331 } 332 } 333 334 manifestPath := filepath.Join(tempDir, manifestFileName) 335 f, err := os.OpenFile(manifestPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644) 336 if err != nil { 337 return err 338 } 339 340 if err := json.NewEncoder(f).Encode(manifest); err != nil { 341 f.Close() 342 return err 343 } 344 345 f.Close() 346 347 if err := system.Chtimes(manifestPath, time.Unix(0, 0), time.Unix(0, 0)); err != nil { 348 return err 349 } 350 351 const ociLayoutContent = `{"imageLayoutVersion": "` + ocispec.ImageLayoutVersion + `"}` 352 layoutPath := filepath.Join(tempDir, ocispec.ImageLayoutFile) 353 if err := os.WriteFile(layoutPath, []byte(ociLayoutContent), 0o644); err != nil { 354 return errors.Wrap(err, "error writing oci layout file") 355 } 356 if err := system.Chtimes(layoutPath, time.Unix(0, 0), time.Unix(0, 0)); err != nil { 357 return errors.Wrap(err, "error setting oci layout file timestamps") 358 } 359 360 data, err := json.Marshal(ocispec.Index{ 361 Versioned: specs.Versioned{ 362 SchemaVersion: 2, 363 }, 364 MediaType: ocispec.MediaTypeImageIndex, 365 Manifests: manifestDescriptors, 366 }) 367 if err != nil { 368 return errors.Wrap(err, "error marshaling oci index") 369 } 370 371 idxFile := filepath.Join(s.outDir, ocispec.ImageIndexFile) 372 if err := os.WriteFile(idxFile, data, 0o644); err != nil { 373 return errors.Wrap(err, "error writing oci index file") 374 } 375 376 fs, err := archive.Tar(tempDir, archive.Uncompressed) 377 if err != nil { 378 return err 379 } 380 defer fs.Close() 381 382 _, err = io.Copy(outStream, fs) 383 return err 384 } 385 386 func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Descriptor, error) { 387 img := s.images[id].image 388 if len(img.RootFS.DiffIDs) == 0 { 389 return nil, fmt.Errorf("empty export - not implemented") 390 } 391 392 var parent digest.Digest 393 var layers []layer.DiffID 394 var foreignSrcs map[layer.DiffID]distribution.Descriptor 395 for i, diffID := range img.RootFS.DiffIDs { 396 v1ImgCreated := time.Unix(0, 0) 397 v1Img := image.V1Image{ 398 // This is for backward compatibility used for 399 // pre v1.9 docker. 400 Created: &v1ImgCreated, 401 } 402 if i == len(img.RootFS.DiffIDs)-1 { 403 v1Img = img.V1Image 404 } 405 rootFS := *img.RootFS 406 rootFS.DiffIDs = rootFS.DiffIDs[:i+1] 407 v1ID, err := v1.CreateID(v1Img, rootFS.ChainID(), parent) 408 if err != nil { 409 return nil, err 410 } 411 412 v1Img.ID = v1ID.Encoded() 413 if parent != "" { 414 v1Img.Parent = parent.Encoded() 415 } 416 417 v1Img.OS = img.OS 418 src, err := s.saveConfigAndLayer(rootFS.ChainID(), v1Img, img.Created) 419 if err != nil { 420 return nil, err 421 } 422 423 layers = append(layers, diffID) 424 parent = v1ID 425 if src.Digest != "" { 426 if foreignSrcs == nil { 427 foreignSrcs = make(map[layer.DiffID]distribution.Descriptor) 428 } 429 foreignSrcs[img.RootFS.DiffIDs[i]] = src 430 } 431 } 432 433 data := img.RawJSON() 434 dgst := digest.FromBytes(data) 435 436 blobDir := filepath.Join(s.outDir, ocispec.ImageBlobsDir, dgst.Algorithm().String()) 437 if err := os.MkdirAll(blobDir, 0o755); err != nil { 438 return nil, err 439 } 440 if img.Created != nil { 441 if err := system.Chtimes(blobDir, *img.Created, *img.Created); err != nil { 442 return nil, err 443 } 444 if err := system.Chtimes(filepath.Dir(blobDir), *img.Created, *img.Created); err != nil { 445 return nil, err 446 } 447 } 448 449 configFile := filepath.Join(blobDir, dgst.Encoded()) 450 if err := os.WriteFile(configFile, img.RawJSON(), 0o644); err != nil { 451 return nil, err 452 } 453 if img.Created != nil { 454 if err := system.Chtimes(configFile, *img.Created, *img.Created); err != nil { 455 return nil, err 456 } 457 } 458 459 s.images[id].layers = layers 460 return foreignSrcs, nil 461 } 462 463 func (s *saveSession) saveConfigAndLayer(id layer.ChainID, legacyImg image.V1Image, createdTime *time.Time) (distribution.Descriptor, error) { 464 outDir := filepath.Join(s.outDir, ocispec.ImageBlobsDir) 465 466 if _, ok := s.savedConfigs[legacyImg.ID]; !ok { 467 if err := s.saveConfig(legacyImg, outDir, createdTime); err != nil { 468 return distribution.Descriptor{}, err 469 } 470 } 471 472 // serialize filesystem 473 l, err := s.lss.Get(id) 474 if err != nil { 475 return distribution.Descriptor{}, err 476 } 477 478 lDiffID := l.DiffID() 479 lDgst := digest.Digest(lDiffID) 480 if _, ok := s.savedLayers[lDiffID]; ok { 481 return s.savedLayers[lDiffID], nil 482 } 483 layerPath := filepath.Join(outDir, lDgst.Algorithm().String(), lDgst.Encoded()) 484 defer layer.ReleaseAndLog(s.lss, l) 485 486 if _, err = os.Stat(layerPath); err == nil { 487 // This is should not happen. If the layer path was already created, we should have returned early. 488 // Log a warning an proceed to recreate the archive. 489 log.G(context.TODO()).WithFields(log.Fields{ 490 "layerPath": layerPath, 491 "id": id, 492 "lDgst": lDgst, 493 }).Warn("LayerPath already exists but the descriptor is not cached") 494 } else if !os.IsNotExist(err) { 495 return distribution.Descriptor{}, err 496 } 497 498 // We use sequential file access to avoid depleting the standby list on 499 // Windows. On Linux, this equates to a regular os.Create. 500 if err := os.MkdirAll(filepath.Dir(layerPath), 0o755); err != nil { 501 return distribution.Descriptor{}, errors.Wrap(err, "could not create layer dir parent") 502 } 503 tarFile, err := sequential.Create(layerPath) 504 if err != nil { 505 return distribution.Descriptor{}, errors.Wrap(err, "error creating layer file") 506 } 507 defer tarFile.Close() 508 509 arch, err := l.TarStream() 510 if err != nil { 511 return distribution.Descriptor{}, err 512 } 513 defer arch.Close() 514 515 digester := digest.Canonical.Digester() 516 digestedArch := io.TeeReader(arch, digester.Hash()) 517 518 tarSize, err := io.Copy(tarFile, digestedArch) 519 if err != nil { 520 return distribution.Descriptor{}, err 521 } 522 523 tarDigest := digester.Digest() 524 if lDgst != tarDigest { 525 log.G(context.TODO()).WithFields(log.Fields{ 526 "layerDigest": lDgst, 527 "actualDigest": tarDigest, 528 }).Warn("layer digest doesn't match its tar archive digest") 529 530 lDgst = digester.Digest() 531 layerPath = filepath.Join(outDir, lDgst.Algorithm().String(), lDgst.Encoded()) 532 } 533 534 if createdTime != nil { 535 for _, fname := range []string{outDir, layerPath} { 536 // todo: maybe save layer created timestamp? 537 if err := system.Chtimes(fname, *createdTime, *createdTime); err != nil { 538 return distribution.Descriptor{}, errors.Wrap(err, "could not set layer timestamp") 539 } 540 } 541 } 542 543 var desc distribution.Descriptor 544 if fs, ok := l.(distribution.Describable); ok { 545 desc = fs.Descriptor() 546 } 547 548 if desc.Digest == "" { 549 desc.Digest = tarDigest 550 desc.Size = tarSize 551 } 552 if desc.MediaType == "" { 553 desc.MediaType = ocispec.MediaTypeImageLayer 554 } 555 s.savedLayers[lDiffID] = desc 556 557 return desc, nil 558 } 559 560 func (s *saveSession) saveConfig(legacyImg image.V1Image, outDir string, createdTime *time.Time) error { 561 imageConfig, err := json.Marshal(legacyImg) 562 if err != nil { 563 return err 564 } 565 566 cfgDgst := digest.FromBytes(imageConfig) 567 configPath := filepath.Join(outDir, cfgDgst.Algorithm().String(), cfgDgst.Encoded()) 568 if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil { 569 return errors.Wrap(err, "could not create layer dir parent") 570 } 571 572 if err := os.WriteFile(configPath, imageConfig, 0o644); err != nil { 573 return err 574 } 575 576 if createdTime != nil { 577 if err := system.Chtimes(configPath, *createdTime, *createdTime); err != nil { 578 return errors.Wrap(err, "could not set config timestamp") 579 } 580 } 581 582 s.savedConfigs[legacyImg.ID] = struct{}{} 583 return nil 584 }