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