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