github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/image/tarexport/save.go (about) 1 package tarexport // import "github.com/docker/docker/image/tarexport" 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "os" 8 "path" 9 "path/filepath" 10 "runtime" 11 "time" 12 13 "github.com/docker/distribution" 14 "github.com/docker/distribution/reference" 15 "github.com/docker/docker/image" 16 v1 "github.com/docker/docker/image/v1" 17 "github.com/docker/docker/layer" 18 "github.com/docker/docker/pkg/archive" 19 "github.com/docker/docker/pkg/system" 20 digest "github.com/opencontainers/go-digest" 21 "github.com/pkg/errors" 22 ) 23 24 type imageDescriptor struct { 25 refs []reference.NamedTagged 26 layers []string 27 image *image.Image 28 layerRef layer.Layer 29 } 30 31 type saveSession struct { 32 *tarexporter 33 outDir string 34 images map[image.ID]*imageDescriptor 35 savedLayers map[string]struct{} 36 diffIDPaths map[layer.DiffID]string // cache every diffID blob to avoid duplicates 37 } 38 39 func (l *tarexporter) Save(names []string, outStream io.Writer) error { 40 images, err := l.parseNames(names) 41 if err != nil { 42 return err 43 } 44 45 // Release all the image top layer references 46 defer l.releaseLayerReferences(images) 47 return (&saveSession{tarexporter: l, images: images}).save(outStream) 48 } 49 50 // parseNames will parse the image names to a map which contains image.ID to *imageDescriptor. 51 // Each imageDescriptor holds an image top layer reference named 'layerRef'. It is taken here, should be released later. 52 func (l *tarexporter) parseNames(names []string) (desc map[image.ID]*imageDescriptor, rErr error) { 53 imgDescr := make(map[image.ID]*imageDescriptor) 54 defer func() { 55 if rErr != nil { 56 l.releaseLayerReferences(imgDescr) 57 } 58 }() 59 60 addAssoc := func(id image.ID, ref reference.Named) error { 61 if _, ok := imgDescr[id]; !ok { 62 descr := &imageDescriptor{} 63 if err := l.takeLayerReference(id, descr); err != nil { 64 return err 65 } 66 imgDescr[id] = descr 67 } 68 69 if ref != nil { 70 if _, ok := ref.(reference.Canonical); ok { 71 return nil 72 } 73 tagged, ok := reference.TagNameOnly(ref).(reference.NamedTagged) 74 if !ok { 75 return nil 76 } 77 78 for _, t := range imgDescr[id].refs { 79 if tagged.String() == t.String() { 80 return nil 81 } 82 } 83 imgDescr[id].refs = append(imgDescr[id].refs, tagged) 84 } 85 return nil 86 } 87 88 for _, name := range names { 89 ref, err := reference.ParseAnyReference(name) 90 if err != nil { 91 return nil, err 92 } 93 namedRef, ok := ref.(reference.Named) 94 if !ok { 95 // Check if digest ID reference 96 if digested, ok := ref.(reference.Digested); ok { 97 id := image.IDFromDigest(digested.Digest()) 98 if err := addAssoc(id, nil); err != nil { 99 return nil, err 100 } 101 continue 102 } 103 return nil, errors.Errorf("invalid reference: %v", name) 104 } 105 106 if reference.FamiliarName(namedRef) == string(digest.Canonical) { 107 imgID, err := l.is.Search(name) 108 if err != nil { 109 return nil, err 110 } 111 if err := addAssoc(imgID, nil); err != nil { 112 return nil, err 113 } 114 continue 115 } 116 if reference.IsNameOnly(namedRef) { 117 assocs := l.rs.ReferencesByName(namedRef) 118 for _, assoc := range assocs { 119 if err := addAssoc(image.IDFromDigest(assoc.ID), assoc.Ref); err != nil { 120 return nil, err 121 } 122 } 123 if len(assocs) == 0 { 124 imgID, err := l.is.Search(name) 125 if err != nil { 126 return nil, err 127 } 128 if err := addAssoc(imgID, nil); err != nil { 129 return nil, err 130 } 131 } 132 continue 133 } 134 id, err := l.rs.Get(namedRef) 135 if err != nil { 136 return nil, err 137 } 138 if err := addAssoc(image.IDFromDigest(id), namedRef); err != nil { 139 return nil, err 140 } 141 142 } 143 return imgDescr, nil 144 } 145 146 // takeLayerReference will take/Get the image top layer reference 147 func (l *tarexporter) takeLayerReference(id image.ID, imgDescr *imageDescriptor) error { 148 img, err := l.is.Get(id) 149 if err != nil { 150 return err 151 } 152 imgDescr.image = img 153 topLayerID := img.RootFS.ChainID() 154 if topLayerID == "" { 155 return nil 156 } 157 os := img.OS 158 if os == "" { 159 os = runtime.GOOS 160 } 161 if !system.IsOSSupported(os) { 162 return fmt.Errorf("os %q is not supported", os) 163 } 164 layer, err := l.lss[os].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 os := descr.image.OS 177 if os == "" { 178 os = runtime.GOOS 179 } 180 l.lss[os].Release(descr.layerRef) 181 } 182 } 183 return nil 184 } 185 186 func (s *saveSession) save(outStream io.Writer) error { 187 s.savedLayers = make(map[string]struct{}) 188 s.diffIDPaths = make(map[layer.DiffID]string) 189 190 // get image json 191 tempDir, err := os.MkdirTemp("", "docker-export-") 192 if err != nil { 193 return err 194 } 195 defer os.RemoveAll(tempDir) 196 197 s.outDir = tempDir 198 reposLegacy := make(map[string]map[string]string) 199 200 var manifest []manifestItem 201 var parentLinks []parentLink 202 203 for id, imageDescr := range s.images { 204 foreignSrcs, err := s.saveImage(id) 205 if err != nil { 206 return err 207 } 208 209 var repoTags []string 210 var layers []string 211 212 for _, ref := range imageDescr.refs { 213 familiarName := reference.FamiliarName(ref) 214 if _, ok := reposLegacy[familiarName]; !ok { 215 reposLegacy[familiarName] = make(map[string]string) 216 } 217 reposLegacy[familiarName][ref.Tag()] = imageDescr.layers[len(imageDescr.layers)-1] 218 repoTags = append(repoTags, reference.FamiliarString(ref)) 219 } 220 221 for _, l := range imageDescr.layers { 222 // IMPORTANT: We use path, not filepath here to ensure the layers 223 // in the manifest use Unix-style forward-slashes. Otherwise, a 224 // Linux image saved from LCOW won't be able to be imported on 225 // LCOL. 226 layers = append(layers, path.Join(l, legacyLayerFileName)) 227 } 228 229 manifest = append(manifest, manifestItem{ 230 Config: id.Digest().Hex() + ".json", 231 RepoTags: repoTags, 232 Layers: layers, 233 LayerSources: foreignSrcs, 234 }) 235 236 parentID, _ := s.is.GetParent(id) 237 parentLinks = append(parentLinks, parentLink{id, parentID}) 238 s.tarexporter.loggerImgEvent.LogImageEvent(id.String(), id.String(), "save") 239 } 240 241 for i, p := range validatedParentLinks(parentLinks) { 242 if p.parentID != "" { 243 manifest[i].Parent = p.parentID 244 } 245 } 246 247 if len(reposLegacy) > 0 { 248 reposFile := filepath.Join(tempDir, legacyRepositoriesFileName) 249 rf, err := os.OpenFile(reposFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) 250 if err != nil { 251 return err 252 } 253 254 if err := json.NewEncoder(rf).Encode(reposLegacy); err != nil { 255 rf.Close() 256 return err 257 } 258 259 rf.Close() 260 261 if err := system.Chtimes(reposFile, time.Unix(0, 0), time.Unix(0, 0)); err != nil { 262 return err 263 } 264 } 265 266 manifestFileName := filepath.Join(tempDir, manifestFileName) 267 f, err := os.OpenFile(manifestFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) 268 if err != nil { 269 return err 270 } 271 272 if err := json.NewEncoder(f).Encode(manifest); err != nil { 273 f.Close() 274 return err 275 } 276 277 f.Close() 278 279 if err := system.Chtimes(manifestFileName, time.Unix(0, 0), time.Unix(0, 0)); err != nil { 280 return err 281 } 282 283 fs, err := archive.Tar(tempDir, archive.Uncompressed) 284 if err != nil { 285 return err 286 } 287 defer fs.Close() 288 289 _, err = io.Copy(outStream, fs) 290 return err 291 } 292 293 func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Descriptor, error) { 294 img := s.images[id].image 295 if len(img.RootFS.DiffIDs) == 0 { 296 return nil, fmt.Errorf("empty export - not implemented") 297 } 298 299 var parent digest.Digest 300 var layers []string 301 var foreignSrcs map[layer.DiffID]distribution.Descriptor 302 for i := range img.RootFS.DiffIDs { 303 v1Img := image.V1Image{ 304 // This is for backward compatibility used for 305 // pre v1.9 docker. 306 Created: time.Unix(0, 0), 307 } 308 if i == len(img.RootFS.DiffIDs)-1 { 309 v1Img = img.V1Image 310 } 311 rootFS := *img.RootFS 312 rootFS.DiffIDs = rootFS.DiffIDs[:i+1] 313 v1ID, err := v1.CreateID(v1Img, rootFS.ChainID(), parent) 314 if err != nil { 315 return nil, err 316 } 317 318 v1Img.ID = v1ID.Hex() 319 if parent != "" { 320 v1Img.Parent = parent.Hex() 321 } 322 323 v1Img.OS = img.OS 324 src, err := s.saveLayer(rootFS.ChainID(), v1Img, img.Created) 325 if err != nil { 326 return nil, err 327 } 328 layers = append(layers, v1Img.ID) 329 parent = v1ID 330 if src.Digest != "" { 331 if foreignSrcs == nil { 332 foreignSrcs = make(map[layer.DiffID]distribution.Descriptor) 333 } 334 foreignSrcs[img.RootFS.DiffIDs[i]] = src 335 } 336 } 337 338 configFile := filepath.Join(s.outDir, id.Digest().Hex()+".json") 339 if err := os.WriteFile(configFile, img.RawJSON(), 0644); err != nil { 340 return nil, err 341 } 342 if err := system.Chtimes(configFile, img.Created, img.Created); err != nil { 343 return nil, err 344 } 345 346 s.images[id].layers = layers 347 return foreignSrcs, nil 348 } 349 350 func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, createdTime time.Time) (distribution.Descriptor, error) { 351 if _, exists := s.savedLayers[legacyImg.ID]; exists { 352 return distribution.Descriptor{}, nil 353 } 354 355 outDir := filepath.Join(s.outDir, legacyImg.ID) 356 if err := os.Mkdir(outDir, 0755); err != nil { 357 return distribution.Descriptor{}, err 358 } 359 360 // todo: why is this version file here? 361 if err := os.WriteFile(filepath.Join(outDir, legacyVersionFileName), []byte("1.0"), 0644); err != nil { 362 return distribution.Descriptor{}, err 363 } 364 365 imageConfig, err := json.Marshal(legacyImg) 366 if err != nil { 367 return distribution.Descriptor{}, err 368 } 369 370 if err := os.WriteFile(filepath.Join(outDir, legacyConfigFileName), imageConfig, 0644); err != nil { 371 return distribution.Descriptor{}, err 372 } 373 374 // serialize filesystem 375 layerPath := filepath.Join(outDir, legacyLayerFileName) 376 operatingSystem := legacyImg.OS 377 if operatingSystem == "" { 378 operatingSystem = runtime.GOOS 379 } 380 l, err := s.lss[operatingSystem].Get(id) 381 if err != nil { 382 return distribution.Descriptor{}, err 383 } 384 defer layer.ReleaseAndLog(s.lss[operatingSystem], l) 385 386 if oldPath, exists := s.diffIDPaths[l.DiffID()]; exists { 387 relPath, err := filepath.Rel(outDir, oldPath) 388 if err != nil { 389 return distribution.Descriptor{}, err 390 } 391 if err := os.Symlink(relPath, layerPath); err != nil { 392 return distribution.Descriptor{}, errors.Wrap(err, "error creating symlink while saving layer") 393 } 394 } else { 395 // Use system.CreateSequential rather than os.Create. This ensures sequential 396 // file access on Windows to avoid eating into MM standby list. 397 // On Linux, this equates to a regular os.Create. 398 tarFile, err := system.CreateSequential(layerPath) 399 if err != nil { 400 return distribution.Descriptor{}, err 401 } 402 defer tarFile.Close() 403 404 arch, err := l.TarStream() 405 if err != nil { 406 return distribution.Descriptor{}, err 407 } 408 defer arch.Close() 409 410 if _, err := io.Copy(tarFile, arch); err != nil { 411 return distribution.Descriptor{}, err 412 } 413 414 for _, fname := range []string{"", legacyVersionFileName, legacyConfigFileName, legacyLayerFileName} { 415 // todo: maybe save layer created timestamp? 416 if err := system.Chtimes(filepath.Join(outDir, fname), createdTime, createdTime); err != nil { 417 return distribution.Descriptor{}, err 418 } 419 } 420 421 s.diffIDPaths[l.DiffID()] = layerPath 422 } 423 s.savedLayers[legacyImg.ID] = struct{}{} 424 425 var src distribution.Descriptor 426 if fs, ok := l.(distribution.Describable); ok { 427 src = fs.Descriptor() 428 } 429 return src, nil 430 }