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