github.com/stevegt/moby@v1.13.1/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/digest" 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/docker/docker/reference" 20 ) 21 22 type imageDescriptor struct { 23 refs []reference.NamedTagged 24 layers []string 25 } 26 27 type saveSession struct { 28 *tarexporter 29 outDir string 30 images map[image.ID]*imageDescriptor 31 savedLayers map[string]struct{} 32 diffIDPaths map[layer.DiffID]string // cache every diffID blob to avoid duplicates 33 } 34 35 func (l *tarexporter) Save(names []string, outStream io.Writer) error { 36 images, err := l.parseNames(names) 37 if err != nil { 38 return err 39 } 40 41 return (&saveSession{tarexporter: l, images: images}).save(outStream) 42 } 43 44 func (l *tarexporter) parseNames(names []string) (map[image.ID]*imageDescriptor, error) { 45 imgDescr := make(map[image.ID]*imageDescriptor) 46 47 addAssoc := func(id image.ID, ref reference.Named) { 48 if _, ok := imgDescr[id]; !ok { 49 imgDescr[id] = &imageDescriptor{} 50 } 51 52 if ref != nil { 53 var tagged reference.NamedTagged 54 if _, ok := ref.(reference.Canonical); ok { 55 return 56 } 57 var ok bool 58 if tagged, ok = ref.(reference.NamedTagged); !ok { 59 var err error 60 if tagged, err = reference.WithTag(ref, reference.DefaultTag); err != nil { 61 return 62 } 63 } 64 65 for _, t := range imgDescr[id].refs { 66 if tagged.String() == t.String() { 67 return 68 } 69 } 70 imgDescr[id].refs = append(imgDescr[id].refs, tagged) 71 } 72 } 73 74 for _, name := range names { 75 id, ref, err := reference.ParseIDOrReference(name) 76 if err != nil { 77 return nil, err 78 } 79 if id != "" { 80 _, err := l.is.Get(image.IDFromDigest(id)) 81 if err != nil { 82 return nil, err 83 } 84 addAssoc(image.IDFromDigest(id), nil) 85 continue 86 } 87 if ref.Name() == string(digest.Canonical) { 88 imgID, err := l.is.Search(name) 89 if err != nil { 90 return nil, err 91 } 92 addAssoc(imgID, nil) 93 continue 94 } 95 if reference.IsNameOnly(ref) { 96 assocs := l.rs.ReferencesByName(ref) 97 for _, assoc := range assocs { 98 addAssoc(image.IDFromDigest(assoc.ID), assoc.Ref) 99 } 100 if len(assocs) == 0 { 101 imgID, err := l.is.Search(name) 102 if err != nil { 103 return nil, err 104 } 105 addAssoc(imgID, nil) 106 } 107 continue 108 } 109 id, err = l.rs.Get(ref) 110 if err != nil { 111 return nil, err 112 } 113 addAssoc(image.IDFromDigest(id), ref) 114 115 } 116 return imgDescr, nil 117 } 118 119 func (s *saveSession) save(outStream io.Writer) error { 120 s.savedLayers = make(map[string]struct{}) 121 s.diffIDPaths = make(map[layer.DiffID]string) 122 123 // get image json 124 tempDir, err := ioutil.TempDir("", "docker-export-") 125 if err != nil { 126 return err 127 } 128 defer os.RemoveAll(tempDir) 129 130 s.outDir = tempDir 131 reposLegacy := make(map[string]map[string]string) 132 133 var manifest []manifestItem 134 var parentLinks []parentLink 135 136 for id, imageDescr := range s.images { 137 foreignSrcs, err := s.saveImage(id) 138 if err != nil { 139 return err 140 } 141 142 var repoTags []string 143 var layers []string 144 145 for _, ref := range imageDescr.refs { 146 if _, ok := reposLegacy[ref.Name()]; !ok { 147 reposLegacy[ref.Name()] = make(map[string]string) 148 } 149 reposLegacy[ref.Name()][ref.Tag()] = imageDescr.layers[len(imageDescr.layers)-1] 150 repoTags = append(repoTags, ref.String()) 151 } 152 153 for _, l := range imageDescr.layers { 154 layers = append(layers, filepath.Join(l, legacyLayerFileName)) 155 } 156 157 manifest = append(manifest, manifestItem{ 158 Config: id.Digest().Hex() + ".json", 159 RepoTags: repoTags, 160 Layers: layers, 161 LayerSources: foreignSrcs, 162 }) 163 164 parentID, _ := s.is.GetParent(id) 165 parentLinks = append(parentLinks, parentLink{id, parentID}) 166 s.tarexporter.loggerImgEvent.LogImageEvent(id.String(), id.String(), "save") 167 } 168 169 for i, p := range validatedParentLinks(parentLinks) { 170 if p.parentID != "" { 171 manifest[i].Parent = p.parentID 172 } 173 } 174 175 if len(reposLegacy) > 0 { 176 reposFile := filepath.Join(tempDir, legacyRepositoriesFileName) 177 rf, err := os.OpenFile(reposFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) 178 if err != nil { 179 return err 180 } 181 182 if err := json.NewEncoder(rf).Encode(reposLegacy); err != nil { 183 rf.Close() 184 return err 185 } 186 187 rf.Close() 188 189 if err := system.Chtimes(reposFile, time.Unix(0, 0), time.Unix(0, 0)); err != nil { 190 return err 191 } 192 } 193 194 manifestFileName := filepath.Join(tempDir, manifestFileName) 195 f, err := os.OpenFile(manifestFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) 196 if err != nil { 197 return err 198 } 199 200 if err := json.NewEncoder(f).Encode(manifest); err != nil { 201 f.Close() 202 return err 203 } 204 205 f.Close() 206 207 if err := system.Chtimes(manifestFileName, time.Unix(0, 0), time.Unix(0, 0)); err != nil { 208 return err 209 } 210 211 fs, err := archive.Tar(tempDir, archive.Uncompressed) 212 if err != nil { 213 return err 214 } 215 defer fs.Close() 216 217 if _, err := io.Copy(outStream, fs); err != nil { 218 return err 219 } 220 return nil 221 } 222 223 func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Descriptor, error) { 224 img, err := s.is.Get(id) 225 if err != nil { 226 return nil, err 227 } 228 229 if len(img.RootFS.DiffIDs) == 0 { 230 return nil, fmt.Errorf("empty export - not implemented") 231 } 232 233 var parent digest.Digest 234 var layers []string 235 var foreignSrcs map[layer.DiffID]distribution.Descriptor 236 for i := range img.RootFS.DiffIDs { 237 v1Img := image.V1Image{ 238 Created: img.Created, 239 } 240 if i == len(img.RootFS.DiffIDs)-1 { 241 v1Img = img.V1Image 242 } 243 rootFS := *img.RootFS 244 rootFS.DiffIDs = rootFS.DiffIDs[:i+1] 245 v1ID, err := v1.CreateID(v1Img, rootFS.ChainID(), parent) 246 if err != nil { 247 return nil, err 248 } 249 250 v1Img.ID = v1ID.Hex() 251 if parent != "" { 252 v1Img.Parent = parent.Hex() 253 } 254 255 src, err := s.saveLayer(rootFS.ChainID(), v1Img, img.Created) 256 if err != nil { 257 return nil, err 258 } 259 layers = append(layers, v1Img.ID) 260 parent = v1ID 261 if src.Digest != "" { 262 if foreignSrcs == nil { 263 foreignSrcs = make(map[layer.DiffID]distribution.Descriptor) 264 } 265 foreignSrcs[img.RootFS.DiffIDs[i]] = src 266 } 267 } 268 269 configFile := filepath.Join(s.outDir, id.Digest().Hex()+".json") 270 if err := ioutil.WriteFile(configFile, img.RawJSON(), 0644); err != nil { 271 return nil, err 272 } 273 if err := system.Chtimes(configFile, img.Created, img.Created); err != nil { 274 return nil, err 275 } 276 277 s.images[id].layers = layers 278 return foreignSrcs, nil 279 } 280 281 func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, createdTime time.Time) (distribution.Descriptor, error) { 282 if _, exists := s.savedLayers[legacyImg.ID]; exists { 283 return distribution.Descriptor{}, nil 284 } 285 286 outDir := filepath.Join(s.outDir, legacyImg.ID) 287 if err := os.Mkdir(outDir, 0755); err != nil { 288 return distribution.Descriptor{}, err 289 } 290 291 // todo: why is this version file here? 292 if err := ioutil.WriteFile(filepath.Join(outDir, legacyVersionFileName), []byte("1.0"), 0644); err != nil { 293 return distribution.Descriptor{}, err 294 } 295 296 imageConfig, err := json.Marshal(legacyImg) 297 if err != nil { 298 return distribution.Descriptor{}, err 299 } 300 301 if err := ioutil.WriteFile(filepath.Join(outDir, legacyConfigFileName), imageConfig, 0644); err != nil { 302 return distribution.Descriptor{}, err 303 } 304 305 // serialize filesystem 306 layerPath := filepath.Join(outDir, legacyLayerFileName) 307 l, err := s.ls.Get(id) 308 if err != nil { 309 return distribution.Descriptor{}, err 310 } 311 defer layer.ReleaseAndLog(s.ls, l) 312 313 if oldPath, exists := s.diffIDPaths[l.DiffID()]; exists { 314 relPath, err := filepath.Rel(outDir, oldPath) 315 if err != nil { 316 return distribution.Descriptor{}, err 317 } 318 os.Symlink(relPath, layerPath) 319 } else { 320 // Use system.CreateSequential rather than os.Create. This ensures sequential 321 // file access on Windows to avoid eating into MM standby list. 322 // On Linux, this equates to a regular os.Create. 323 tarFile, err := system.CreateSequential(layerPath) 324 if err != nil { 325 return distribution.Descriptor{}, err 326 } 327 defer tarFile.Close() 328 329 arch, err := l.TarStream() 330 if err != nil { 331 return distribution.Descriptor{}, err 332 } 333 defer arch.Close() 334 335 if _, err := io.Copy(tarFile, arch); err != nil { 336 return distribution.Descriptor{}, err 337 } 338 339 for _, fname := range []string{"", legacyVersionFileName, legacyConfigFileName, legacyLayerFileName} { 340 // todo: maybe save layer created timestamp? 341 if err := system.Chtimes(filepath.Join(outDir, fname), createdTime, createdTime); err != nil { 342 return distribution.Descriptor{}, err 343 } 344 } 345 346 s.diffIDPaths[l.DiffID()] = layerPath 347 } 348 s.savedLayers[legacyImg.ID] = struct{}{} 349 350 var src distribution.Descriptor 351 if fs, ok := l.(distribution.Describable); ok { 352 src = fs.Descriptor() 353 } 354 return src, nil 355 }