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