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