1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/migrate/v1/migratev1.go
Derek McGowan d04fa49a0d Refactor RWLayer to use referenced object instead of string
RWLayer will now have more operations and be protected through a referenced type rather than always looked up by string in the layer store.
Separates creation of RWLayer (write capture layer) from mounting of the layer.
This allows mount labels to be applied after creation and allowing RWLayer objects to have the same lifespan as a container without performance regressions from requiring mount.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
2015-12-23 11:19:17 -08:00

360 lines
8.8 KiB
Go

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"
"github.com/docker/docker/reference"
)
type graphIDRegistrar interface {
RegisterByGraphID(string, layer.ChainID, string) (layer.Layer, error)
Release(layer.Layer) ([]layer.Metadata, error)
}
type graphIDMounter interface {
CreateRWLayerByGraphID(string, string, layer.ChainID) error
}
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.
func Migrate(root, driverName string, ls layer.Store, is image.Store, rs reference.Store, ms metadata.Store) error {
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
}
if err := migrateRefs(root, driverName, rs, mappings); err != nil {
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
}
if err := ls.CreateRWLayerByGraphID(id, id, img.RootFS.ChainID()); err != nil {
return err
}
logrus.Infof("migrated container %s to point to %s", id, imageID)
}
return nil
}
type refAdder interface {
AddTag(ref reference.Named, id image.ID, force bool) error
AddDigest(ref reference.Canonical, id image.ID, 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.WithName(name)
if err != nil {
logrus.Errorf("migrate tags: invalid name %q, %q", name, err)
continue
}
if dgst, err := digest.ParseDigest(tag); err == nil {
canonical, err := reference.WithDigest(ref, dgst)
if err != nil {
logrus.Errorf("migrate tags: invalid digest %q, %q", dgst, err)
continue
}
if err := rs.AddDigest(canonical, strongID, false); err != nil {
logrus.Errorf("can't migrate digest %q for %q, err: %q", ref.String(), 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, false); err != nil {
logrus.Errorf("can't migrate tag %q for %q, err: %q", ref.String(), 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 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)
}