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