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