github.com/rawahars/moby@v24.0.4+incompatible/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 "time" 11 12 "github.com/docker/distribution" 13 "github.com/docker/distribution/reference" 14 "github.com/docker/docker/image" 15 v1 "github.com/docker/docker/image/v1" 16 "github.com/docker/docker/layer" 17 "github.com/docker/docker/pkg/archive" 18 "github.com/docker/docker/pkg/system" 19 "github.com/moby/sys/sequential" 20 "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 if err := addAssoc(image.ID(digested.Digest()), nil); err != nil { 98 return nil, err 99 } 100 continue 101 } 102 return nil, errors.Errorf("invalid reference: %v", name) 103 } 104 105 if reference.FamiliarName(namedRef) == string(digest.Canonical) { 106 imgID, err := l.is.Search(name) 107 if err != nil { 108 return nil, err 109 } 110 if err := addAssoc(imgID, nil); err != nil { 111 return nil, err 112 } 113 continue 114 } 115 if reference.IsNameOnly(namedRef) { 116 assocs := l.rs.ReferencesByName(namedRef) 117 for _, assoc := range assocs { 118 if err := addAssoc(image.ID(assoc.ID), assoc.Ref); err != nil { 119 return nil, err 120 } 121 } 122 if len(assocs) == 0 { 123 imgID, err := l.is.Search(name) 124 if err != nil { 125 return nil, err 126 } 127 if err := addAssoc(imgID, nil); err != nil { 128 return nil, err 129 } 130 } 131 continue 132 } 133 id, err := l.rs.Get(namedRef) 134 if err != nil { 135 return nil, err 136 } 137 if err := addAssoc(image.ID(id), namedRef); err != nil { 138 return nil, err 139 } 140 } 141 return imgDescr, nil 142 } 143 144 // takeLayerReference will take/Get the image top layer reference 145 func (l *tarexporter) takeLayerReference(id image.ID, imgDescr *imageDescriptor) error { 146 img, err := l.is.Get(id) 147 if err != nil { 148 return err 149 } 150 if os := img.OperatingSystem(); !system.IsOSSupported(os) { 151 return fmt.Errorf("os %q is not supported", os) 152 } 153 imgDescr.image = img 154 topLayerID := img.RootFS.ChainID() 155 if topLayerID == "" { 156 return nil 157 } 158 layer, err := l.lss.Get(topLayerID) 159 if err != nil { 160 return err 161 } 162 imgDescr.layerRef = layer 163 return nil 164 } 165 166 // releaseLayerReferences will release all the image top layer references 167 func (l *tarexporter) releaseLayerReferences(imgDescr map[image.ID]*imageDescriptor) error { 168 for _, descr := range imgDescr { 169 if descr.layerRef != nil { 170 l.lss.Release(descr.layerRef) 171 } 172 } 173 return nil 174 } 175 176 func (s *saveSession) save(outStream io.Writer) error { 177 s.savedLayers = make(map[string]struct{}) 178 s.diffIDPaths = make(map[layer.DiffID]string) 179 180 // get image json 181 tempDir, err := os.MkdirTemp("", "docker-export-") 182 if err != nil { 183 return err 184 } 185 defer os.RemoveAll(tempDir) 186 187 s.outDir = tempDir 188 reposLegacy := make(map[string]map[string]string) 189 190 var manifest []manifestItem 191 var parentLinks []parentLink 192 193 for id, imageDescr := range s.images { 194 foreignSrcs, err := s.saveImage(id) 195 if err != nil { 196 return err 197 } 198 199 var repoTags []string 200 var layers []string 201 202 for _, ref := range imageDescr.refs { 203 familiarName := reference.FamiliarName(ref) 204 if _, ok := reposLegacy[familiarName]; !ok { 205 reposLegacy[familiarName] = make(map[string]string) 206 } 207 reposLegacy[familiarName][ref.Tag()] = imageDescr.layers[len(imageDescr.layers)-1] 208 repoTags = append(repoTags, reference.FamiliarString(ref)) 209 } 210 211 for _, l := range imageDescr.layers { 212 // IMPORTANT: We use path, not filepath here to ensure the layers 213 // in the manifest use Unix-style forward-slashes. 214 layers = append(layers, path.Join(l, legacyLayerFileName)) 215 } 216 217 manifest = append(manifest, manifestItem{ 218 Config: id.Digest().Encoded() + ".json", 219 RepoTags: repoTags, 220 Layers: layers, 221 LayerSources: foreignSrcs, 222 }) 223 224 parentID, _ := s.is.GetParent(id) 225 parentLinks = append(parentLinks, parentLink{id, parentID}) 226 s.tarexporter.loggerImgEvent.LogImageEvent(id.String(), id.String(), "save") 227 } 228 229 for i, p := range validatedParentLinks(parentLinks) { 230 if p.parentID != "" { 231 manifest[i].Parent = p.parentID 232 } 233 } 234 235 if len(reposLegacy) > 0 { 236 reposFile := filepath.Join(tempDir, legacyRepositoriesFileName) 237 rf, err := os.OpenFile(reposFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) 238 if err != nil { 239 return err 240 } 241 242 if err := json.NewEncoder(rf).Encode(reposLegacy); err != nil { 243 rf.Close() 244 return err 245 } 246 247 rf.Close() 248 249 if err := system.Chtimes(reposFile, time.Unix(0, 0), time.Unix(0, 0)); err != nil { 250 return err 251 } 252 } 253 254 manifestFileName := filepath.Join(tempDir, manifestFileName) 255 f, err := os.OpenFile(manifestFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) 256 if err != nil { 257 return err 258 } 259 260 if err := json.NewEncoder(f).Encode(manifest); err != nil { 261 f.Close() 262 return err 263 } 264 265 f.Close() 266 267 if err := system.Chtimes(manifestFileName, time.Unix(0, 0), time.Unix(0, 0)); err != nil { 268 return err 269 } 270 271 fs, err := archive.Tar(tempDir, archive.Uncompressed) 272 if err != nil { 273 return err 274 } 275 defer fs.Close() 276 277 _, err = io.Copy(outStream, fs) 278 return err 279 } 280 281 func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Descriptor, error) { 282 img := s.images[id].image 283 if len(img.RootFS.DiffIDs) == 0 { 284 return nil, fmt.Errorf("empty export - not implemented") 285 } 286 287 var parent digest.Digest 288 var layers []string 289 var foreignSrcs map[layer.DiffID]distribution.Descriptor 290 for i := range img.RootFS.DiffIDs { 291 v1Img := image.V1Image{ 292 // This is for backward compatibility used for 293 // pre v1.9 docker. 294 Created: time.Unix(0, 0), 295 } 296 if i == len(img.RootFS.DiffIDs)-1 { 297 v1Img = img.V1Image 298 } 299 rootFS := *img.RootFS 300 rootFS.DiffIDs = rootFS.DiffIDs[:i+1] 301 v1ID, err := v1.CreateID(v1Img, rootFS.ChainID(), parent) 302 if err != nil { 303 return nil, err 304 } 305 306 v1Img.ID = v1ID.Encoded() 307 if parent != "" { 308 v1Img.Parent = parent.Encoded() 309 } 310 311 v1Img.OS = img.OS 312 src, err := s.saveLayer(rootFS.ChainID(), v1Img, img.Created) 313 if err != nil { 314 return nil, err 315 } 316 layers = append(layers, v1Img.ID) 317 parent = v1ID 318 if src.Digest != "" { 319 if foreignSrcs == nil { 320 foreignSrcs = make(map[layer.DiffID]distribution.Descriptor) 321 } 322 foreignSrcs[img.RootFS.DiffIDs[i]] = src 323 } 324 } 325 326 configFile := filepath.Join(s.outDir, id.Digest().Encoded()+".json") 327 if err := os.WriteFile(configFile, img.RawJSON(), 0o644); err != nil { 328 return nil, err 329 } 330 if err := system.Chtimes(configFile, img.Created, img.Created); err != nil { 331 return nil, err 332 } 333 334 s.images[id].layers = layers 335 return foreignSrcs, nil 336 } 337 338 func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, createdTime time.Time) (distribution.Descriptor, error) { 339 if _, exists := s.savedLayers[legacyImg.ID]; exists { 340 return distribution.Descriptor{}, nil 341 } 342 343 outDir := filepath.Join(s.outDir, legacyImg.ID) 344 if err := os.Mkdir(outDir, 0755); err != nil { 345 return distribution.Descriptor{}, err 346 } 347 348 // todo: why is this version file here? 349 if err := os.WriteFile(filepath.Join(outDir, legacyVersionFileName), []byte("1.0"), 0644); err != nil { 350 return distribution.Descriptor{}, err 351 } 352 353 imageConfig, err := json.Marshal(legacyImg) 354 if err != nil { 355 return distribution.Descriptor{}, err 356 } 357 358 if err := os.WriteFile(filepath.Join(outDir, legacyConfigFileName), imageConfig, 0644); err != nil { 359 return distribution.Descriptor{}, err 360 } 361 362 // serialize filesystem 363 layerPath := filepath.Join(outDir, legacyLayerFileName) 364 l, err := s.lss.Get(id) 365 if err != nil { 366 return distribution.Descriptor{}, err 367 } 368 defer layer.ReleaseAndLog(s.lss, l) 369 370 if oldPath, exists := s.diffIDPaths[l.DiffID()]; exists { 371 relPath, err := filepath.Rel(outDir, oldPath) 372 if err != nil { 373 return distribution.Descriptor{}, err 374 } 375 if err := os.Symlink(relPath, layerPath); err != nil { 376 return distribution.Descriptor{}, errors.Wrap(err, "error creating symlink while saving layer") 377 } 378 } else { 379 // We use sequential file access to avoid depleting the standby list on 380 // Windows. On Linux, this equates to a regular os.Create. 381 tarFile, err := sequential.Create(layerPath) 382 if err != nil { 383 return distribution.Descriptor{}, err 384 } 385 defer tarFile.Close() 386 387 arch, err := l.TarStream() 388 if err != nil { 389 return distribution.Descriptor{}, err 390 } 391 defer arch.Close() 392 393 if _, err := io.Copy(tarFile, arch); err != nil { 394 return distribution.Descriptor{}, err 395 } 396 397 for _, fname := range []string{"", legacyVersionFileName, legacyConfigFileName, legacyLayerFileName} { 398 // todo: maybe save layer created timestamp? 399 if err := system.Chtimes(filepath.Join(outDir, fname), createdTime, createdTime); err != nil { 400 return distribution.Descriptor{}, err 401 } 402 } 403 404 s.diffIDPaths[l.DiffID()] = layerPath 405 } 406 s.savedLayers[legacyImg.ID] = struct{}{} 407 408 var src distribution.Descriptor 409 if fs, ok := l.(distribution.Describable); ok { 410 src = fs.Descriptor() 411 } 412 return src, nil 413 }