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