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