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