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