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