123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- package v1
- import (
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "runtime"
- "strconv"
- "sync"
- "time"
- "encoding/json"
- "github.com/Sirupsen/logrus"
- "github.com/docker/distribution/reference"
- "github.com/docker/docker/distribution/metadata"
- "github.com/docker/docker/image"
- imagev1 "github.com/docker/docker/image/v1"
- "github.com/docker/docker/layer"
- "github.com/docker/docker/pkg/ioutils"
- refstore "github.com/docker/docker/reference"
- "github.com/opencontainers/go-digest"
- )
- type graphIDRegistrar interface {
- RegisterByGraphID(string, layer.ChainID, layer.DiffID, string, int64) (layer.Layer, error)
- Release(layer.Layer) ([]layer.Metadata, error)
- }
- type graphIDMounter interface {
- CreateRWLayerByGraphID(string, string, layer.ChainID) error
- }
- type checksumCalculator interface {
- ChecksumForGraphID(id, parent, oldTarDataPath, newTarDataPath string) (diffID layer.DiffID, size int64, err error)
- }
- const (
- graphDirName = "graph"
- tarDataFileName = "tar-data.json.gz"
- migrationFileName = ".migration-v1-images.json"
- migrationTagsFileName = ".migration-v1-tags"
- migrationDiffIDFileName = ".migration-diffid"
- migrationSizeFileName = ".migration-size"
- migrationTarDataFileName = ".migration-tardata"
- containersDirName = "containers"
- configFileNameLegacy = "config.json"
- configFileName = "config.v2.json"
- repositoriesFilePrefixLegacy = "repositories-"
- )
- var (
- errUnsupported = errors.New("migration is not supported")
- )
- // Migrate takes an old graph directory and transforms the metadata into the
- // new format.
- func Migrate(root, driverName string, ls layer.Store, is image.Store, rs refstore.Store, ms metadata.Store) error {
- graphDir := filepath.Join(root, graphDirName)
- if _, err := os.Lstat(graphDir); os.IsNotExist(err) {
- return nil
- }
- mappings, err := restoreMappings(root)
- if err != nil {
- return err
- }
- if cc, ok := ls.(checksumCalculator); ok {
- CalculateLayerChecksums(root, cc, mappings)
- }
- if registrar, ok := ls.(graphIDRegistrar); !ok {
- return errUnsupported
- } else if err := migrateImages(root, registrar, is, ms, mappings); err != nil {
- return err
- }
- err = saveMappings(root, mappings)
- if err != nil {
- return err
- }
- if mounter, ok := ls.(graphIDMounter); !ok {
- return errUnsupported
- } else if err := migrateContainers(root, mounter, is, mappings); err != nil {
- return err
- }
- if err := migrateRefs(root, driverName, rs, mappings); err != nil {
- return err
- }
- return nil
- }
- // CalculateLayerChecksums walks an old graph directory and calculates checksums
- // for each layer. These checksums are later used for migration.
- func CalculateLayerChecksums(root string, ls checksumCalculator, mappings map[string]image.ID) {
- graphDir := filepath.Join(root, graphDirName)
- // spawn some extra workers also for maximum performance because the process is bounded by both cpu and io
- workers := runtime.NumCPU() * 3
- workQueue := make(chan string, workers)
- wg := sync.WaitGroup{}
- for i := 0; i < workers; i++ {
- wg.Add(1)
- go func() {
- for id := range workQueue {
- start := time.Now()
- if err := calculateLayerChecksum(graphDir, id, ls); err != nil {
- logrus.Errorf("could not calculate checksum for %q, %q", id, err)
- }
- elapsed := time.Since(start)
- logrus.Debugf("layer %s took %.2f seconds", id, elapsed.Seconds())
- }
- wg.Done()
- }()
- }
- dir, err := ioutil.ReadDir(graphDir)
- if err != nil {
- logrus.Errorf("could not read directory %q", graphDir)
- return
- }
- for _, v := range dir {
- v1ID := v.Name()
- if err := imagev1.ValidateID(v1ID); err != nil {
- continue
- }
- if _, ok := mappings[v1ID]; ok { // support old migrations without helper files
- continue
- }
- workQueue <- v1ID
- }
- close(workQueue)
- wg.Wait()
- }
- func calculateLayerChecksum(graphDir, id string, ls checksumCalculator) error {
- diffIDFile := filepath.Join(graphDir, id, migrationDiffIDFileName)
- if _, err := os.Lstat(diffIDFile); err == nil {
- return nil
- } else if !os.IsNotExist(err) {
- return err
- }
- parent, err := getParent(filepath.Join(graphDir, id))
- if err != nil {
- return err
- }
- diffID, size, err := ls.ChecksumForGraphID(id, parent, filepath.Join(graphDir, id, tarDataFileName), filepath.Join(graphDir, id, migrationTarDataFileName))
- if err != nil {
- return err
- }
- if err := ioutil.WriteFile(filepath.Join(graphDir, id, migrationSizeFileName), []byte(strconv.Itoa(int(size))), 0600); err != nil {
- return err
- }
- if err := ioutils.AtomicWriteFile(filepath.Join(graphDir, id, migrationDiffIDFileName), []byte(diffID), 0600); err != nil {
- return err
- }
- logrus.Infof("calculated checksum for layer %s: %s", id, diffID)
- return nil
- }
- func restoreMappings(root string) (map[string]image.ID, error) {
- mappings := make(map[string]image.ID)
- mfile := filepath.Join(root, migrationFileName)
- f, err := os.Open(mfile)
- if err != nil && !os.IsNotExist(err) {
- return nil, err
- } else if err == nil {
- err := json.NewDecoder(f).Decode(&mappings)
- if err != nil {
- f.Close()
- return nil, err
- }
- f.Close()
- }
- return mappings, nil
- }
- func saveMappings(root string, mappings map[string]image.ID) error {
- mfile := filepath.Join(root, migrationFileName)
- f, err := os.OpenFile(mfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
- if err != nil {
- return err
- }
- defer f.Close()
- return json.NewEncoder(f).Encode(mappings)
- }
- func migrateImages(root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) error {
- graphDir := filepath.Join(root, graphDirName)
- dir, err := ioutil.ReadDir(graphDir)
- if err != nil {
- return err
- }
- for _, v := range dir {
- v1ID := v.Name()
- if err := imagev1.ValidateID(v1ID); err != nil {
- continue
- }
- if _, exists := mappings[v1ID]; exists {
- continue
- }
- if err := migrateImage(v1ID, root, ls, is, ms, mappings); err != nil {
- continue
- }
- }
- return nil
- }
- func migrateContainers(root string, ls graphIDMounter, is image.Store, imageMappings map[string]image.ID) error {
- containersDir := filepath.Join(root, containersDirName)
- dir, err := ioutil.ReadDir(containersDir)
- if err != nil {
- return err
- }
- for _, v := range dir {
- id := v.Name()
- if _, err := os.Stat(filepath.Join(containersDir, id, configFileName)); err == nil {
- continue
- }
- containerJSON, err := ioutil.ReadFile(filepath.Join(containersDir, id, configFileNameLegacy))
- if err != nil {
- logrus.Errorf("migrate container error: %v", err)
- continue
- }
- var c map[string]*json.RawMessage
- if err := json.Unmarshal(containerJSON, &c); err != nil {
- logrus.Errorf("migrate container error: %v", err)
- continue
- }
- imageStrJSON, ok := c["Image"]
- if !ok {
- return fmt.Errorf("invalid container configuration for %v", id)
- }
- var image string
- if err := json.Unmarshal([]byte(*imageStrJSON), &image); err != nil {
- logrus.Errorf("migrate container error: %v", err)
- continue
- }
- imageID, ok := imageMappings[image]
- if !ok {
- logrus.Errorf("image not migrated %v", imageID) // non-fatal error
- continue
- }
- c["Image"] = rawJSON(imageID)
- containerJSON, err = json.Marshal(c)
- if err != nil {
- return err
- }
- if err := ioutil.WriteFile(filepath.Join(containersDir, id, configFileName), containerJSON, 0600); err != nil {
- return err
- }
- img, err := is.Get(imageID)
- if err != nil {
- return err
- }
- if err := ls.CreateRWLayerByGraphID(id, id, img.RootFS.ChainID()); err != nil {
- logrus.Errorf("migrate container error: %v", err)
- continue
- }
- logrus.Infof("migrated container %s to point to %s", id, imageID)
- }
- return nil
- }
- type refAdder interface {
- AddTag(ref reference.Named, id digest.Digest, force bool) error
- AddDigest(ref reference.Canonical, id digest.Digest, force bool) error
- }
- func migrateRefs(root, driverName string, rs refAdder, mappings map[string]image.ID) error {
- migrationFile := filepath.Join(root, migrationTagsFileName)
- if _, err := os.Lstat(migrationFile); !os.IsNotExist(err) {
- return err
- }
- type repositories struct {
- Repositories map[string]map[string]string
- }
- var repos repositories
- f, err := os.Open(filepath.Join(root, repositoriesFilePrefixLegacy+driverName))
- if err != nil {
- if os.IsNotExist(err) {
- return nil
- }
- return err
- }
- defer f.Close()
- if err := json.NewDecoder(f).Decode(&repos); err != nil {
- return err
- }
- for name, repo := range repos.Repositories {
- for tag, id := range repo {
- if strongID, exists := mappings[id]; exists {
- ref, err := reference.ParseNormalizedNamed(name)
- if err != nil {
- logrus.Errorf("migrate tags: invalid name %q, %q", name, err)
- continue
- }
- if !reference.IsNameOnly(ref) {
- logrus.Errorf("migrate tags: invalid name %q, unexpected tag or digest", name)
- continue
- }
- if dgst, err := digest.Parse(tag); err == nil {
- canonical, err := reference.WithDigest(reference.TrimNamed(ref), dgst)
- if err != nil {
- logrus.Errorf("migrate tags: invalid digest %q, %q", dgst, err)
- continue
- }
- if err := rs.AddDigest(canonical, strongID.Digest(), false); err != nil {
- logrus.Errorf("can't migrate digest %q for %q, err: %q", reference.FamiliarString(ref), strongID, err)
- }
- } else {
- tagRef, err := reference.WithTag(ref, tag)
- if err != nil {
- logrus.Errorf("migrate tags: invalid tag %q, %q", tag, err)
- continue
- }
- if err := rs.AddTag(tagRef, strongID.Digest(), false); err != nil {
- logrus.Errorf("can't migrate tag %q for %q, err: %q", reference.FamiliarString(ref), strongID, err)
- }
- }
- logrus.Infof("migrated tag %s:%s to point to %s", name, tag, strongID)
- }
- }
- }
- mf, err := os.Create(migrationFile)
- if err != nil {
- return err
- }
- mf.Close()
- return nil
- }
- func getParent(confDir string) (string, error) {
- jsonFile := filepath.Join(confDir, "json")
- imageJSON, err := ioutil.ReadFile(jsonFile)
- if err != nil {
- return "", err
- }
- var parent struct {
- Parent string
- ParentID digest.Digest `json:"parent_id"`
- }
- if err := json.Unmarshal(imageJSON, &parent); err != nil {
- return "", err
- }
- if parent.Parent == "" && parent.ParentID != "" { // v1.9
- parent.Parent = parent.ParentID.Hex()
- }
- // compatibilityID for parent
- parentCompatibilityID, err := ioutil.ReadFile(filepath.Join(confDir, "parent"))
- if err == nil && len(parentCompatibilityID) > 0 {
- parent.Parent = string(parentCompatibilityID)
- }
- return parent.Parent, nil
- }
- func migrateImage(id, root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) (err error) {
- defer func() {
- if err != nil {
- logrus.Errorf("migration failed for %v, err: %v", id, err)
- }
- }()
- parent, err := getParent(filepath.Join(root, graphDirName, id))
- if err != nil {
- return err
- }
- var parentID image.ID
- if parent != "" {
- var exists bool
- if parentID, exists = mappings[parent]; !exists {
- if err := migrateImage(parent, root, ls, is, ms, mappings); err != nil {
- // todo: fail or allow broken chains?
- return err
- }
- parentID = mappings[parent]
- }
- }
- rootFS := image.NewRootFS()
- var history []image.History
- if parentID != "" {
- parentImg, err := is.Get(parentID)
- if err != nil {
- return err
- }
- rootFS = parentImg.RootFS
- history = parentImg.History
- }
- diffIDData, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, migrationDiffIDFileName))
- if err != nil {
- return err
- }
- diffID, err := digest.Parse(string(diffIDData))
- if err != nil {
- return err
- }
- sizeStr, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, migrationSizeFileName))
- if err != nil {
- return err
- }
- size, err := strconv.ParseInt(string(sizeStr), 10, 64)
- if err != nil {
- return err
- }
- layer, err := ls.RegisterByGraphID(id, rootFS.ChainID(), layer.DiffID(diffID), filepath.Join(root, graphDirName, id, migrationTarDataFileName), size)
- if err != nil {
- return err
- }
- logrus.Infof("migrated layer %s to %s", id, layer.DiffID())
- jsonFile := filepath.Join(root, graphDirName, id, "json")
- imageJSON, err := ioutil.ReadFile(jsonFile)
- if err != nil {
- return err
- }
- h, err := imagev1.HistoryFromConfig(imageJSON, false)
- if err != nil {
- return err
- }
- history = append(history, h)
- rootFS.Append(layer.DiffID())
- config, err := imagev1.MakeConfigFromV1Config(imageJSON, rootFS, history)
- if err != nil {
- return err
- }
- strongID, err := is.Create(config)
- if err != nil {
- return err
- }
- logrus.Infof("migrated image %s to %s", id, strongID)
- if parentID != "" {
- if err := is.SetParent(strongID, parentID); err != nil {
- return err
- }
- }
- checksum, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, "checksum"))
- if err == nil { // best effort
- dgst, err := digest.Parse(string(checksum))
- if err == nil {
- V2MetadataService := metadata.NewV2MetadataService(ms)
- V2MetadataService.Add(layer.DiffID(), metadata.V2Metadata{Digest: dgst})
- }
- }
- _, err = ls.Release(layer)
- if err != nil {
- return err
- }
- mappings[id] = strongID
- return
- }
- func rawJSON(value interface{}) *json.RawMessage {
- jsonval, err := json.Marshal(value)
- if err != nil {
- return nil
- }
- return (*json.RawMessage)(&jsonval)
- }
|