github.com/resin-io/docker@v1.13.1/image/tarexport/load.go (about) 1 package tarexport 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "reflect" 11 12 "github.com/Sirupsen/logrus" 13 "github.com/docker/distribution" 14 "github.com/docker/distribution/digest" 15 "github.com/docker/docker/image" 16 "github.com/docker/docker/image/v1" 17 "github.com/docker/docker/layer" 18 "github.com/docker/docker/pkg/archive" 19 "github.com/docker/docker/pkg/chrootarchive" 20 "github.com/docker/docker/pkg/progress" 21 "github.com/docker/docker/pkg/streamformatter" 22 "github.com/docker/docker/pkg/stringid" 23 "github.com/docker/docker/pkg/symlink" 24 "github.com/docker/docker/pkg/system" 25 "github.com/docker/docker/reference" 26 ) 27 28 func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool) error { 29 var ( 30 sf = streamformatter.NewJSONStreamFormatter() 31 progressOutput progress.Output 32 ) 33 if !quiet { 34 progressOutput = sf.NewProgressOutput(outStream, false) 35 } 36 outStream = &streamformatter.StdoutFormatter{Writer: outStream, StreamFormatter: streamformatter.NewJSONStreamFormatter()} 37 38 tmpDir, err := ioutil.TempDir("", "docker-import-") 39 if err != nil { 40 return err 41 } 42 defer os.RemoveAll(tmpDir) 43 44 if err := chrootarchive.Untar(inTar, tmpDir, nil); err != nil { 45 return err 46 } 47 // read manifest, if no file then load in legacy mode 48 manifestPath, err := safePath(tmpDir, manifestFileName) 49 if err != nil { 50 return err 51 } 52 manifestFile, err := os.Open(manifestPath) 53 if err != nil { 54 if os.IsNotExist(err) { 55 return l.legacyLoad(tmpDir, outStream, progressOutput) 56 } 57 return err 58 } 59 defer manifestFile.Close() 60 61 var manifest []manifestItem 62 if err := json.NewDecoder(manifestFile).Decode(&manifest); err != nil { 63 return err 64 } 65 66 var parentLinks []parentLink 67 var imageIDsStr string 68 var imageRefCount int 69 70 for _, m := range manifest { 71 configPath, err := safePath(tmpDir, m.Config) 72 if err != nil { 73 return err 74 } 75 config, err := ioutil.ReadFile(configPath) 76 if err != nil { 77 return err 78 } 79 img, err := image.NewFromJSON(config) 80 if err != nil { 81 return err 82 } 83 var rootFS image.RootFS 84 rootFS = *img.RootFS 85 rootFS.DiffIDs = nil 86 87 if expected, actual := len(m.Layers), len(img.RootFS.DiffIDs); expected != actual { 88 return fmt.Errorf("invalid manifest, layers length mismatch: expected %q, got %q", expected, actual) 89 } 90 91 for i, diffID := range img.RootFS.DiffIDs { 92 layerPath, err := safePath(tmpDir, m.Layers[i]) 93 if err != nil { 94 return err 95 } 96 r := rootFS 97 r.Append(diffID) 98 newLayer, err := l.ls.Get(r.ChainID()) 99 if err != nil { 100 newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), m.LayerSources[diffID], progressOutput) 101 if err != nil { 102 return err 103 } 104 } 105 defer layer.ReleaseAndLog(l.ls, newLayer) 106 if expected, actual := diffID, newLayer.DiffID(); expected != actual { 107 return fmt.Errorf("invalid diffID for layer %d: expected %q, got %q", i, expected, actual) 108 } 109 rootFS.Append(diffID) 110 } 111 112 imgID, err := l.is.Create(config) 113 if err != nil { 114 return err 115 } 116 imageIDsStr += fmt.Sprintf("Loaded image ID: %s\n", imgID) 117 118 imageRefCount = 0 119 for _, repoTag := range m.RepoTags { 120 named, err := reference.ParseNamed(repoTag) 121 if err != nil { 122 return err 123 } 124 ref, ok := named.(reference.NamedTagged) 125 if !ok { 126 return fmt.Errorf("invalid tag %q", repoTag) 127 } 128 l.setLoadedTag(ref, imgID.Digest(), outStream) 129 outStream.Write([]byte(fmt.Sprintf("Loaded image: %s\n", ref))) 130 imageRefCount++ 131 } 132 133 parentLinks = append(parentLinks, parentLink{imgID, m.Parent}) 134 l.loggerImgEvent.LogImageEvent(imgID.String(), imgID.String(), "load") 135 } 136 137 for _, p := range validatedParentLinks(parentLinks) { 138 if p.parentID != "" { 139 if err := l.setParentID(p.id, p.parentID); err != nil { 140 return err 141 } 142 } 143 } 144 145 if imageRefCount == 0 { 146 outStream.Write([]byte(imageIDsStr)) 147 } 148 149 return nil 150 } 151 152 func (l *tarexporter) setParentID(id, parentID image.ID) error { 153 img, err := l.is.Get(id) 154 if err != nil { 155 return err 156 } 157 parent, err := l.is.Get(parentID) 158 if err != nil { 159 return err 160 } 161 if !checkValidParent(img, parent) { 162 return fmt.Errorf("image %v is not a valid parent for %v", parent.ID(), img.ID()) 163 } 164 return l.is.SetParent(id, parentID) 165 } 166 167 func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, foreignSrc distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) { 168 // We use system.OpenSequential to use sequential file access on Windows, avoiding 169 // depleting the standby list. On Linux, this equates to a regular os.Open. 170 rawTar, err := system.OpenSequential(filename) 171 if err != nil { 172 logrus.Debugf("Error reading embedded tar: %v", err) 173 return nil, err 174 } 175 defer rawTar.Close() 176 177 var r io.Reader 178 if progressOutput != nil { 179 fileInfo, err := rawTar.Stat() 180 if err != nil { 181 logrus.Debugf("Error statting file: %v", err) 182 return nil, err 183 } 184 185 r = progress.NewProgressReader(rawTar, progressOutput, fileInfo.Size(), stringid.TruncateID(id), "Loading layer") 186 } else { 187 r = rawTar 188 } 189 190 inflatedLayerData, err := archive.DecompressStream(r) 191 if err != nil { 192 return nil, err 193 } 194 defer inflatedLayerData.Close() 195 196 if ds, ok := l.ls.(layer.DescribableStore); ok { 197 return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), foreignSrc) 198 } 199 return l.ls.Register(inflatedLayerData, rootFS.ChainID()) 200 } 201 202 func (l *tarexporter) setLoadedTag(ref reference.NamedTagged, imgID digest.Digest, outStream io.Writer) error { 203 if prevID, err := l.rs.Get(ref); err == nil && prevID != imgID { 204 fmt.Fprintf(outStream, "The image %s already exists, renaming the old one with ID %s to empty string\n", ref.String(), string(prevID)) // todo: this message is wrong in case of multiple tags 205 } 206 207 if err := l.rs.AddTag(ref, imgID, true); err != nil { 208 return err 209 } 210 return nil 211 } 212 213 func (l *tarexporter) legacyLoad(tmpDir string, outStream io.Writer, progressOutput progress.Output) error { 214 legacyLoadedMap := make(map[string]image.ID) 215 216 dirs, err := ioutil.ReadDir(tmpDir) 217 if err != nil { 218 return err 219 } 220 221 // every dir represents an image 222 for _, d := range dirs { 223 if d.IsDir() { 224 if err := l.legacyLoadImage(d.Name(), tmpDir, legacyLoadedMap, progressOutput); err != nil { 225 return err 226 } 227 } 228 } 229 230 // load tags from repositories file 231 repositoriesPath, err := safePath(tmpDir, legacyRepositoriesFileName) 232 if err != nil { 233 return err 234 } 235 repositoriesFile, err := os.Open(repositoriesPath) 236 if err != nil { 237 return err 238 } 239 defer repositoriesFile.Close() 240 241 repositories := make(map[string]map[string]string) 242 if err := json.NewDecoder(repositoriesFile).Decode(&repositories); err != nil { 243 return err 244 } 245 246 for name, tagMap := range repositories { 247 for tag, oldID := range tagMap { 248 imgID, ok := legacyLoadedMap[oldID] 249 if !ok { 250 return fmt.Errorf("invalid target ID: %v", oldID) 251 } 252 named, err := reference.WithName(name) 253 if err != nil { 254 return err 255 } 256 ref, err := reference.WithTag(named, tag) 257 if err != nil { 258 return err 259 } 260 l.setLoadedTag(ref, imgID.Digest(), outStream) 261 } 262 } 263 264 return nil 265 } 266 267 func (l *tarexporter) legacyLoadImage(oldID, sourceDir string, loadedMap map[string]image.ID, progressOutput progress.Output) error { 268 if _, loaded := loadedMap[oldID]; loaded { 269 return nil 270 } 271 configPath, err := safePath(sourceDir, filepath.Join(oldID, legacyConfigFileName)) 272 if err != nil { 273 return err 274 } 275 imageJSON, err := ioutil.ReadFile(configPath) 276 if err != nil { 277 logrus.Debugf("Error reading json: %v", err) 278 return err 279 } 280 281 var img struct{ Parent string } 282 if err := json.Unmarshal(imageJSON, &img); err != nil { 283 return err 284 } 285 286 var parentID image.ID 287 if img.Parent != "" { 288 for { 289 var loaded bool 290 if parentID, loaded = loadedMap[img.Parent]; !loaded { 291 if err := l.legacyLoadImage(img.Parent, sourceDir, loadedMap, progressOutput); err != nil { 292 return err 293 } 294 } else { 295 break 296 } 297 } 298 } 299 300 // todo: try to connect with migrate code 301 rootFS := image.NewRootFS() 302 var history []image.History 303 304 if parentID != "" { 305 parentImg, err := l.is.Get(parentID) 306 if err != nil { 307 return err 308 } 309 310 rootFS = parentImg.RootFS 311 history = parentImg.History 312 } 313 314 layerPath, err := safePath(sourceDir, filepath.Join(oldID, legacyLayerFileName)) 315 if err != nil { 316 return err 317 } 318 newLayer, err := l.loadLayer(layerPath, *rootFS, oldID, distribution.Descriptor{}, progressOutput) 319 if err != nil { 320 return err 321 } 322 rootFS.Append(newLayer.DiffID()) 323 324 h, err := v1.HistoryFromConfig(imageJSON, false) 325 if err != nil { 326 return err 327 } 328 history = append(history, h) 329 330 config, err := v1.MakeConfigFromV1Config(imageJSON, rootFS, history) 331 if err != nil { 332 return err 333 } 334 imgID, err := l.is.Create(config) 335 if err != nil { 336 return err 337 } 338 339 metadata, err := l.ls.Release(newLayer) 340 layer.LogReleaseMetadata(metadata) 341 if err != nil { 342 return err 343 } 344 345 if parentID != "" { 346 if err := l.is.SetParent(imgID, parentID); err != nil { 347 return err 348 } 349 } 350 351 loadedMap[oldID] = imgID 352 return nil 353 } 354 355 func safePath(base, path string) (string, error) { 356 return symlink.FollowSymlinkInScope(filepath.Join(base, path), base) 357 } 358 359 type parentLink struct { 360 id, parentID image.ID 361 } 362 363 func validatedParentLinks(pl []parentLink) (ret []parentLink) { 364 mainloop: 365 for i, p := range pl { 366 ret = append(ret, p) 367 for _, p2 := range pl { 368 if p2.id == p.parentID && p2.id != p.id { 369 continue mainloop 370 } 371 } 372 ret[i].parentID = "" 373 } 374 return 375 } 376 377 func checkValidParent(img, parent *image.Image) bool { 378 if len(img.History) == 0 && len(parent.History) == 0 { 379 return true // having history is not mandatory 380 } 381 if len(img.History)-len(parent.History) != 1 { 382 return false 383 } 384 for i, h := range parent.History { 385 if !reflect.DeepEqual(h, img.History[i]) { 386 return false 387 } 388 } 389 return true 390 }