2015-11-18 17:19:45 -05:00
|
|
|
package v1
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
|
|
"github.com/docker/distribution/digest"
|
|
|
|
"github.com/docker/docker/distribution/metadata"
|
|
|
|
"github.com/docker/docker/image"
|
|
|
|
imagev1 "github.com/docker/docker/image/v1"
|
|
|
|
"github.com/docker/docker/layer"
|
2015-12-04 16:55:15 -05:00
|
|
|
"github.com/docker/docker/reference"
|
2015-11-18 17:19:45 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type graphIDRegistrar interface {
|
|
|
|
RegisterByGraphID(string, layer.ChainID, string) (layer.Layer, error)
|
|
|
|
Release(layer.Layer) ([]layer.Metadata, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type graphIDMounter interface {
|
2015-12-16 17:13:50 -05:00
|
|
|
CreateRWLayerByGraphID(string, string, layer.ChainID) error
|
2015-11-18 17:19:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
graphDirName = "graph"
|
|
|
|
tarDataFileName = "tar-data.json.gz"
|
|
|
|
migrationFileName = ".migration-v1-images.json"
|
|
|
|
migrationTagsFileName = ".migration-v1-tags"
|
|
|
|
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.
|
2015-12-04 16:55:15 -05:00
|
|
|
func Migrate(root, driverName string, ls layer.Store, is image.Store, rs reference.Store, ms metadata.Store) error {
|
2015-11-18 17:19:45 -05:00
|
|
|
mappings := make(map[string]image.ID)
|
|
|
|
|
|
|
|
if registrar, ok := ls.(graphIDRegistrar); !ok {
|
|
|
|
return errUnsupported
|
|
|
|
} else if err := migrateImages(root, registrar, is, ms, mappings); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if mounter, ok := ls.(graphIDMounter); !ok {
|
|
|
|
return errUnsupported
|
|
|
|
} else if err := migrateContainers(root, mounter, is, mappings); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-12-04 16:55:15 -05:00
|
|
|
if err := migrateRefs(root, driverName, rs, mappings); err != nil {
|
2015-11-18 17:19:45 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func migrateImages(root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) error {
|
|
|
|
graphDir := filepath.Join(root, graphDirName)
|
|
|
|
if _, err := os.Lstat(graphDir); err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
mfile := filepath.Join(root, migrationFileName)
|
|
|
|
f, err := os.Open(mfile)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
} else if err == nil {
|
|
|
|
err := json.NewDecoder(f).Decode(&mappings)
|
|
|
|
if err != nil {
|
|
|
|
f.Close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
f.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
f, err = os.OpenFile(mfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
if err := json.NewEncoder(f).Encode(mappings); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var c map[string]*json.RawMessage
|
|
|
|
if err := json.Unmarshal(containerJSON, &c); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-12-16 17:13:50 -05:00
|
|
|
if err := ls.CreateRWLayerByGraphID(id, id, img.RootFS.ChainID()); err != nil {
|
2015-11-18 17:19:45 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Infof("migrated container %s to point to %s", id, imageID)
|
|
|
|
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-12-04 16:55:15 -05:00
|
|
|
type refAdder interface {
|
2015-11-25 15:42:40 -05:00
|
|
|
AddTag(ref reference.Named, id image.ID, force bool) error
|
|
|
|
AddDigest(ref reference.Canonical, id image.ID, force bool) error
|
2015-11-18 17:19:45 -05:00
|
|
|
}
|
|
|
|
|
2015-12-04 16:55:15 -05:00
|
|
|
func migrateRefs(root, driverName string, rs refAdder, mappings map[string]image.ID) error {
|
2015-11-18 17:19:45 -05:00
|
|
|
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.WithName(name)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("migrate tags: invalid name %q, %q", name, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if dgst, err := digest.ParseDigest(tag); err == nil {
|
2015-11-25 15:42:40 -05:00
|
|
|
canonical, err := reference.WithDigest(ref, dgst)
|
2015-11-18 17:19:45 -05:00
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("migrate tags: invalid digest %q, %q", dgst, err)
|
|
|
|
continue
|
|
|
|
}
|
2015-12-04 16:55:15 -05:00
|
|
|
if err := rs.AddDigest(canonical, strongID, false); err != nil {
|
2015-11-25 15:42:40 -05:00
|
|
|
logrus.Errorf("can't migrate digest %q for %q, err: %q", ref.String(), strongID, err)
|
|
|
|
}
|
2015-11-18 17:19:45 -05:00
|
|
|
} else {
|
2015-11-25 15:42:40 -05:00
|
|
|
tagRef, err := reference.WithTag(ref, tag)
|
2015-11-18 17:19:45 -05:00
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("migrate tags: invalid tag %q, %q", tag, err)
|
|
|
|
continue
|
|
|
|
}
|
2015-12-04 16:55:15 -05:00
|
|
|
if err := rs.AddTag(tagRef, strongID, false); err != nil {
|
2015-11-25 15:42:40 -05:00
|
|
|
logrus.Errorf("can't migrate tag %q for %q, err: %q", ref.String(), strongID, err)
|
|
|
|
}
|
2015-11-18 17:19:45 -05:00
|
|
|
}
|
|
|
|
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 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)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
jsonFile := filepath.Join(root, graphDirName, id, "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(root, graphDirName, id, "parent"))
|
|
|
|
if err == nil && len(parentCompatibilityID) > 0 {
|
|
|
|
parent.Parent = string(parentCompatibilityID)
|
|
|
|
}
|
|
|
|
|
|
|
|
var parentID image.ID
|
|
|
|
if parent.Parent != "" {
|
|
|
|
var exists bool
|
|
|
|
if parentID, exists = mappings[parent.Parent]; !exists {
|
|
|
|
if err := migrateImage(parent.Parent, root, ls, is, ms, mappings); err != nil {
|
|
|
|
// todo: fail or allow broken chains?
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
parentID = mappings[parent.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
|
|
|
|
}
|
|
|
|
|
|
|
|
layer, err := ls.RegisterByGraphID(id, rootFS.ChainID(), filepath.Join(filepath.Join(root, graphDirName, id, tarDataFileName)))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
logrus.Infof("migrated layer %s to %s", id, layer.DiffID())
|
|
|
|
|
|
|
|
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.ParseDigest(string(checksum))
|
|
|
|
if err == nil {
|
|
|
|
blobSumService := metadata.NewBlobSumService(ms)
|
|
|
|
blobSumService.Add(layer.DiffID(), 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)
|
|
|
|
}
|