github.com/uriddle/docker@v0.0.0-20210926094723-4072e6aeb013/migrate/v1/migratev1.go (about) 1 package v1 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "runtime" 10 "strconv" 11 "sync" 12 "time" 13 14 "encoding/json" 15 16 "github.com/Sirupsen/logrus" 17 "github.com/docker/distribution/digest" 18 "github.com/docker/docker/distribution/metadata" 19 "github.com/docker/docker/image" 20 imagev1 "github.com/docker/docker/image/v1" 21 "github.com/docker/docker/layer" 22 "github.com/docker/docker/reference" 23 ) 24 25 type graphIDRegistrar interface { 26 RegisterByGraphID(string, layer.ChainID, layer.DiffID, string, int64) (layer.Layer, error) 27 Release(layer.Layer) ([]layer.Metadata, error) 28 } 29 30 type graphIDMounter interface { 31 CreateRWLayerByGraphID(string, string, layer.ChainID) error 32 } 33 34 type checksumCalculator interface { 35 ChecksumForGraphID(id, parent, oldTarDataPath, newTarDataPath string) (diffID layer.DiffID, size int64, err error) 36 } 37 38 const ( 39 graphDirName = "graph" 40 tarDataFileName = "tar-data.json.gz" 41 migrationFileName = ".migration-v1-images.json" 42 migrationTagsFileName = ".migration-v1-tags" 43 migrationDiffIDFileName = ".migration-diffid" 44 migrationSizeFileName = ".migration-size" 45 migrationTarDataFileName = ".migration-tardata" 46 containersDirName = "containers" 47 configFileNameLegacy = "config.json" 48 configFileName = "config.v2.json" 49 repositoriesFilePrefixLegacy = "repositories-" 50 ) 51 52 var ( 53 errUnsupported = errors.New("migration is not supported") 54 ) 55 56 // Migrate takes an old graph directory and transforms the metadata into the 57 // new format. 58 func Migrate(root, driverName string, ls layer.Store, is image.Store, rs reference.Store, ms metadata.Store) error { 59 graphDir := filepath.Join(root, graphDirName) 60 if _, err := os.Lstat(graphDir); os.IsNotExist(err) { 61 return nil 62 } 63 64 mappings, err := restoreMappings(root) 65 if err != nil { 66 return err 67 } 68 69 if cc, ok := ls.(checksumCalculator); ok { 70 CalculateLayerChecksums(root, cc, mappings) 71 } 72 73 if registrar, ok := ls.(graphIDRegistrar); !ok { 74 return errUnsupported 75 } else if err := migrateImages(root, registrar, is, ms, mappings); err != nil { 76 return err 77 } 78 79 err = saveMappings(root, mappings) 80 if err != nil { 81 return err 82 } 83 84 if mounter, ok := ls.(graphIDMounter); !ok { 85 return errUnsupported 86 } else if err := migrateContainers(root, mounter, is, mappings); err != nil { 87 return err 88 } 89 90 if err := migrateRefs(root, driverName, rs, mappings); err != nil { 91 return err 92 } 93 94 return nil 95 } 96 97 // CalculateLayerChecksums walks an old graph directory and calculates checksums 98 // for each layer. These checksums are later used for migration. 99 func CalculateLayerChecksums(root string, ls checksumCalculator, mappings map[string]image.ID) { 100 graphDir := filepath.Join(root, graphDirName) 101 // spawn some extra workers also for maximum performance because the process is bounded by both cpu and io 102 workers := runtime.NumCPU() * 3 103 workQueue := make(chan string, workers) 104 105 wg := sync.WaitGroup{} 106 107 for i := 0; i < workers; i++ { 108 wg.Add(1) 109 go func() { 110 for id := range workQueue { 111 start := time.Now() 112 if err := calculateLayerChecksum(graphDir, id, ls); err != nil { 113 logrus.Errorf("could not calculate checksum for %q, %q", id, err) 114 } 115 elapsed := time.Since(start) 116 logrus.Debugf("layer %s took %.2f seconds", id, elapsed.Seconds()) 117 } 118 wg.Done() 119 }() 120 } 121 122 dir, err := ioutil.ReadDir(graphDir) 123 if err != nil { 124 logrus.Errorf("could not read directory %q", graphDir) 125 return 126 } 127 for _, v := range dir { 128 v1ID := v.Name() 129 if err := imagev1.ValidateID(v1ID); err != nil { 130 continue 131 } 132 if _, ok := mappings[v1ID]; ok { // support old migrations without helper files 133 continue 134 } 135 workQueue <- v1ID 136 } 137 close(workQueue) 138 wg.Wait() 139 } 140 141 func calculateLayerChecksum(graphDir, id string, ls checksumCalculator) error { 142 diffIDFile := filepath.Join(graphDir, id, migrationDiffIDFileName) 143 if _, err := os.Lstat(diffIDFile); err == nil { 144 return nil 145 } else if !os.IsNotExist(err) { 146 return err 147 } 148 149 parent, err := getParent(filepath.Join(graphDir, id)) 150 if err != nil { 151 return err 152 } 153 154 diffID, size, err := ls.ChecksumForGraphID(id, parent, filepath.Join(graphDir, id, tarDataFileName), filepath.Join(graphDir, id, migrationTarDataFileName)) 155 if err != nil { 156 return err 157 } 158 159 if err := ioutil.WriteFile(filepath.Join(graphDir, id, migrationSizeFileName), []byte(strconv.Itoa(int(size))), 0600); err != nil { 160 return err 161 } 162 163 if err := ioutil.WriteFile(filepath.Join(graphDir, id, migrationDiffIDFileName), []byte(diffID), 0600); err != nil { 164 return err 165 } 166 167 logrus.Infof("calculated checksum for layer %s: %s", id, diffID) 168 return nil 169 } 170 171 func restoreMappings(root string) (map[string]image.ID, error) { 172 mappings := make(map[string]image.ID) 173 174 mfile := filepath.Join(root, migrationFileName) 175 f, err := os.Open(mfile) 176 if err != nil && !os.IsNotExist(err) { 177 return nil, err 178 } else if err == nil { 179 err := json.NewDecoder(f).Decode(&mappings) 180 if err != nil { 181 f.Close() 182 return nil, err 183 } 184 f.Close() 185 } 186 187 return mappings, nil 188 } 189 190 func saveMappings(root string, mappings map[string]image.ID) error { 191 mfile := filepath.Join(root, migrationFileName) 192 f, err := os.OpenFile(mfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 193 if err != nil { 194 return err 195 } 196 defer f.Close() 197 if err := json.NewEncoder(f).Encode(mappings); err != nil { 198 return err 199 } 200 return nil 201 } 202 203 func migrateImages(root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) error { 204 graphDir := filepath.Join(root, graphDirName) 205 206 dir, err := ioutil.ReadDir(graphDir) 207 if err != nil { 208 return err 209 } 210 for _, v := range dir { 211 v1ID := v.Name() 212 if err := imagev1.ValidateID(v1ID); err != nil { 213 continue 214 } 215 if _, exists := mappings[v1ID]; exists { 216 continue 217 } 218 if err := migrateImage(v1ID, root, ls, is, ms, mappings); err != nil { 219 continue 220 } 221 } 222 223 return nil 224 } 225 226 func migrateContainers(root string, ls graphIDMounter, is image.Store, imageMappings map[string]image.ID) error { 227 containersDir := filepath.Join(root, containersDirName) 228 dir, err := ioutil.ReadDir(containersDir) 229 if err != nil { 230 return err 231 } 232 for _, v := range dir { 233 id := v.Name() 234 235 if _, err := os.Stat(filepath.Join(containersDir, id, configFileName)); err == nil { 236 continue 237 } 238 239 containerJSON, err := ioutil.ReadFile(filepath.Join(containersDir, id, configFileNameLegacy)) 240 if err != nil { 241 logrus.Errorf("migrate container error: %v", err) 242 continue 243 } 244 245 var c map[string]*json.RawMessage 246 if err := json.Unmarshal(containerJSON, &c); err != nil { 247 logrus.Errorf("migrate container error: %v", err) 248 continue 249 } 250 251 imageStrJSON, ok := c["Image"] 252 if !ok { 253 return fmt.Errorf("invalid container configuration for %v", id) 254 } 255 256 var image string 257 if err := json.Unmarshal([]byte(*imageStrJSON), &image); err != nil { 258 logrus.Errorf("migrate container error: %v", err) 259 continue 260 } 261 262 imageID, ok := imageMappings[image] 263 if !ok { 264 logrus.Errorf("image not migrated %v", imageID) // non-fatal error 265 continue 266 } 267 268 c["Image"] = rawJSON(imageID) 269 270 containerJSON, err = json.Marshal(c) 271 if err != nil { 272 return err 273 } 274 275 if err := ioutil.WriteFile(filepath.Join(containersDir, id, configFileName), containerJSON, 0600); err != nil { 276 return err 277 } 278 279 img, err := is.Get(imageID) 280 if err != nil { 281 return err 282 } 283 284 if err := ls.CreateRWLayerByGraphID(id, id, img.RootFS.ChainID()); err != nil { 285 logrus.Errorf("migrate container error: %v", err) 286 continue 287 } 288 289 logrus.Infof("migrated container %s to point to %s", id, imageID) 290 291 } 292 return nil 293 } 294 295 type refAdder interface { 296 AddTag(ref reference.Named, id image.ID, force bool) error 297 AddDigest(ref reference.Canonical, id image.ID, force bool) error 298 } 299 300 func migrateRefs(root, driverName string, rs refAdder, mappings map[string]image.ID) error { 301 migrationFile := filepath.Join(root, migrationTagsFileName) 302 if _, err := os.Lstat(migrationFile); !os.IsNotExist(err) { 303 return err 304 } 305 306 type repositories struct { 307 Repositories map[string]map[string]string 308 } 309 310 var repos repositories 311 312 f, err := os.Open(filepath.Join(root, repositoriesFilePrefixLegacy+driverName)) 313 if err != nil { 314 if os.IsNotExist(err) { 315 return nil 316 } 317 return err 318 } 319 defer f.Close() 320 if err := json.NewDecoder(f).Decode(&repos); err != nil { 321 return err 322 } 323 324 for name, repo := range repos.Repositories { 325 for tag, id := range repo { 326 if strongID, exists := mappings[id]; exists { 327 ref, err := reference.WithName(name) 328 if err != nil { 329 logrus.Errorf("migrate tags: invalid name %q, %q", name, err) 330 continue 331 } 332 if dgst, err := digest.ParseDigest(tag); err == nil { 333 canonical, err := reference.WithDigest(ref, dgst) 334 if err != nil { 335 logrus.Errorf("migrate tags: invalid digest %q, %q", dgst, err) 336 continue 337 } 338 if err := rs.AddDigest(canonical, strongID, false); err != nil { 339 logrus.Errorf("can't migrate digest %q for %q, err: %q", ref.String(), strongID, err) 340 } 341 } else { 342 tagRef, err := reference.WithTag(ref, tag) 343 if err != nil { 344 logrus.Errorf("migrate tags: invalid tag %q, %q", tag, err) 345 continue 346 } 347 if err := rs.AddTag(tagRef, strongID, false); err != nil { 348 logrus.Errorf("can't migrate tag %q for %q, err: %q", ref.String(), strongID, err) 349 } 350 } 351 logrus.Infof("migrated tag %s:%s to point to %s", name, tag, strongID) 352 } 353 } 354 } 355 356 mf, err := os.Create(migrationFile) 357 if err != nil { 358 return err 359 } 360 mf.Close() 361 362 return nil 363 } 364 365 func getParent(confDir string) (string, error) { 366 jsonFile := filepath.Join(confDir, "json") 367 imageJSON, err := ioutil.ReadFile(jsonFile) 368 if err != nil { 369 return "", err 370 } 371 var parent struct { 372 Parent string 373 ParentID digest.Digest `json:"parent_id"` 374 } 375 if err := json.Unmarshal(imageJSON, &parent); err != nil { 376 return "", err 377 } 378 if parent.Parent == "" && parent.ParentID != "" { // v1.9 379 parent.Parent = parent.ParentID.Hex() 380 } 381 // compatibilityID for parent 382 parentCompatibilityID, err := ioutil.ReadFile(filepath.Join(confDir, "parent")) 383 if err == nil && len(parentCompatibilityID) > 0 { 384 parent.Parent = string(parentCompatibilityID) 385 } 386 return parent.Parent, nil 387 } 388 389 func migrateImage(id, root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) (err error) { 390 defer func() { 391 if err != nil { 392 logrus.Errorf("migration failed for %v, err: %v", id, err) 393 } 394 }() 395 396 parent, err := getParent(filepath.Join(root, graphDirName, id)) 397 if err != nil { 398 return err 399 } 400 401 var parentID image.ID 402 if parent != "" { 403 var exists bool 404 if parentID, exists = mappings[parent]; !exists { 405 if err := migrateImage(parent, root, ls, is, ms, mappings); err != nil { 406 // todo: fail or allow broken chains? 407 return err 408 } 409 parentID = mappings[parent] 410 } 411 } 412 413 rootFS := image.NewRootFS() 414 var history []image.History 415 416 if parentID != "" { 417 parentImg, err := is.Get(parentID) 418 if err != nil { 419 return err 420 } 421 422 rootFS = parentImg.RootFS 423 history = parentImg.History 424 } 425 426 diffID, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, migrationDiffIDFileName)) 427 if err != nil { 428 return err 429 } 430 431 sizeStr, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, migrationSizeFileName)) 432 if err != nil { 433 return err 434 } 435 size, err := strconv.ParseInt(string(sizeStr), 10, 64) 436 if err != nil { 437 return err 438 } 439 440 layer, err := ls.RegisterByGraphID(id, rootFS.ChainID(), layer.DiffID(diffID), filepath.Join(root, graphDirName, id, migrationTarDataFileName), size) 441 if err != nil { 442 return err 443 } 444 logrus.Infof("migrated layer %s to %s", id, layer.DiffID()) 445 446 jsonFile := filepath.Join(root, graphDirName, id, "json") 447 imageJSON, err := ioutil.ReadFile(jsonFile) 448 if err != nil { 449 return err 450 } 451 452 h, err := imagev1.HistoryFromConfig(imageJSON, false) 453 if err != nil { 454 return err 455 } 456 history = append(history, h) 457 458 rootFS.Append(layer.DiffID()) 459 460 config, err := imagev1.MakeConfigFromV1Config(imageJSON, rootFS, history) 461 if err != nil { 462 return err 463 } 464 strongID, err := is.Create(config) 465 if err != nil { 466 return err 467 } 468 logrus.Infof("migrated image %s to %s", id, strongID) 469 470 if parentID != "" { 471 if err := is.SetParent(strongID, parentID); err != nil { 472 return err 473 } 474 } 475 476 checksum, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, "checksum")) 477 if err == nil { // best effort 478 dgst, err := digest.ParseDigest(string(checksum)) 479 if err == nil { 480 V2MetadataService := metadata.NewV2MetadataService(ms) 481 V2MetadataService.Add(layer.DiffID(), metadata.V2Metadata{Digest: dgst}) 482 } 483 } 484 _, err = ls.Release(layer) 485 if err != nil { 486 return err 487 } 488 489 mappings[id] = strongID 490 return 491 } 492 493 func rawJSON(value interface{}) *json.RawMessage { 494 jsonval, err := json.Marshal(value) 495 if err != nil { 496 return nil 497 } 498 return (*json.RawMessage)(&jsonval) 499 }