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