2015-11-18 17:18:07 -05:00
package tarexport
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/image"
"github.com/docker/docker/image/v1"
"github.com/docker/docker/layer"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
2016-02-03 21:31:47 -05:00
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/stringid"
2015-11-18 17:18:07 -05:00
"github.com/docker/docker/pkg/symlink"
2015-12-04 16:55:15 -05:00
"github.com/docker/docker/reference"
2015-11-18 17:18:07 -05:00
)
2016-02-03 21:31:47 -05:00
func ( l * tarexporter ) Load ( inTar io . ReadCloser , outStream io . Writer , quiet bool ) error {
var (
sf = streamformatter . NewJSONStreamFormatter ( )
progressOutput progress . Output
)
if ! quiet {
progressOutput = sf . NewProgressOutput ( outStream , false )
}
2015-11-18 17:18:07 -05:00
tmpDir , err := ioutil . TempDir ( "" , "docker-import-" )
if err != nil {
return err
}
defer os . RemoveAll ( tmpDir )
if err := chrootarchive . Untar ( inTar , tmpDir , nil ) ; err != nil {
return err
}
// read manifest, if no file then load in legacy mode
manifestPath , err := safePath ( tmpDir , manifestFileName )
if err != nil {
return err
}
manifestFile , err := os . Open ( manifestPath )
if err != nil {
if os . IsNotExist ( err ) {
2016-02-03 21:31:47 -05:00
return l . legacyLoad ( tmpDir , outStream , progressOutput )
2015-11-18 17:18:07 -05:00
}
return manifestFile . Close ( )
}
defer manifestFile . Close ( )
var manifest [ ] manifestItem
if err := json . NewDecoder ( manifestFile ) . Decode ( & manifest ) ; err != nil {
return err
}
for _ , m := range manifest {
configPath , err := safePath ( tmpDir , m . Config )
if err != nil {
return err
}
config , err := ioutil . ReadFile ( configPath )
if err != nil {
return err
}
img , err := image . NewFromJSON ( config )
if err != nil {
return err
}
var rootFS image . RootFS
rootFS = * img . RootFS
rootFS . DiffIDs = nil
if expected , actual := len ( m . Layers ) , len ( img . RootFS . DiffIDs ) ; expected != actual {
return fmt . Errorf ( "invalid manifest, layers length mismatch: expected %q, got %q" , expected , actual )
}
for i , diffID := range img . RootFS . DiffIDs {
layerPath , err := safePath ( tmpDir , m . Layers [ i ] )
if err != nil {
return err
}
2015-12-16 16:46:40 -05:00
r := rootFS
r . Append ( diffID )
newLayer , err := l . ls . Get ( r . ChainID ( ) )
2015-11-18 17:18:07 -05:00
if err != nil {
2016-02-03 21:31:47 -05:00
newLayer , err = l . loadLayer ( layerPath , rootFS , diffID . String ( ) , progressOutput )
2015-12-16 16:46:40 -05:00
if err != nil {
return err
}
2015-11-18 17:18:07 -05:00
}
defer layer . ReleaseAndLog ( l . ls , newLayer )
if expected , actual := diffID , newLayer . DiffID ( ) ; expected != actual {
return fmt . Errorf ( "invalid diffID for layer %d: expected %q, got %q" , i , expected , actual )
}
rootFS . Append ( diffID )
}
imgID , err := l . is . Create ( config )
if err != nil {
return err
}
for _ , repoTag := range m . RepoTags {
named , err := reference . ParseNamed ( repoTag )
if err != nil {
return err
}
ref , ok := named . ( reference . NamedTagged )
if ! ok {
return fmt . Errorf ( "invalid tag %q" , repoTag )
}
l . setLoadedTag ( ref , imgID , outStream )
}
}
return nil
}
2016-02-03 21:31:47 -05:00
func ( l * tarexporter ) loadLayer ( filename string , rootFS image . RootFS , id string , progressOutput progress . Output ) ( layer . Layer , error ) {
2015-11-18 17:18:07 -05:00
rawTar , err := os . Open ( filename )
if err != nil {
logrus . Debugf ( "Error reading embedded tar: %v" , err )
return nil , err
}
2015-11-23 20:20:44 -05:00
defer rawTar . Close ( )
2015-11-18 17:18:07 -05:00
inflatedLayerData , err := archive . DecompressStream ( rawTar )
if err != nil {
return nil , err
}
defer inflatedLayerData . Close ( )
2016-02-03 21:31:47 -05:00
if progressOutput != nil {
fileInfo , err := os . Stat ( filename )
if err != nil {
logrus . Debugf ( "Error statting file: %v" , err )
return nil , err
}
progressReader := progress . NewProgressReader ( inflatedLayerData , progressOutput , fileInfo . Size ( ) , stringid . TruncateID ( id ) , "Loading layer" )
return l . ls . Register ( progressReader , rootFS . ChainID ( ) )
}
2015-11-18 17:18:07 -05:00
return l . ls . Register ( inflatedLayerData , rootFS . ChainID ( ) )
}
func ( l * tarexporter ) setLoadedTag ( ref reference . NamedTagged , imgID image . ID , outStream io . Writer ) error {
2015-12-04 16:55:15 -05:00
if prevID , err := l . rs . Get ( ref ) ; err == nil && prevID != imgID {
2015-11-18 17:18:07 -05:00
fmt . Fprintf ( outStream , "The image %s already exists, renaming the old one with ID %s to empty string\n" , ref . String ( ) , string ( prevID ) ) // todo: this message is wrong in case of multiple tags
}
2015-12-04 16:55:15 -05:00
if err := l . rs . AddTag ( ref , imgID , true ) ; err != nil {
2015-11-18 17:18:07 -05:00
return err
}
return nil
}
2016-02-03 21:31:47 -05:00
func ( l * tarexporter ) legacyLoad ( tmpDir string , outStream io . Writer , progressOutput progress . Output ) error {
2015-11-18 17:18:07 -05:00
legacyLoadedMap := make ( map [ string ] image . ID )
dirs , err := ioutil . ReadDir ( tmpDir )
if err != nil {
return err
}
// every dir represents an image
for _ , d := range dirs {
if d . IsDir ( ) {
2016-02-03 21:31:47 -05:00
if err := l . legacyLoadImage ( d . Name ( ) , tmpDir , legacyLoadedMap , progressOutput ) ; err != nil {
2015-11-18 17:18:07 -05:00
return err
}
}
}
// load tags from repositories file
repositoriesPath , err := safePath ( tmpDir , legacyRepositoriesFileName )
if err != nil {
return err
}
repositoriesFile , err := os . Open ( repositoriesPath )
if err != nil {
if ! os . IsNotExist ( err ) {
return err
}
return repositoriesFile . Close ( )
}
defer repositoriesFile . Close ( )
repositories := make ( map [ string ] map [ string ] string )
if err := json . NewDecoder ( repositoriesFile ) . Decode ( & repositories ) ; err != nil {
return err
}
for name , tagMap := range repositories {
for tag , oldID := range tagMap {
imgID , ok := legacyLoadedMap [ oldID ]
if ! ok {
return fmt . Errorf ( "invalid target ID: %v" , oldID )
}
named , err := reference . WithName ( name )
if err != nil {
return err
}
ref , err := reference . WithTag ( named , tag )
if err != nil {
return err
}
l . setLoadedTag ( ref , imgID , outStream )
}
}
return nil
}
2016-02-03 21:31:47 -05:00
func ( l * tarexporter ) legacyLoadImage ( oldID , sourceDir string , loadedMap map [ string ] image . ID , progressOutput progress . Output ) error {
2015-11-18 17:18:07 -05:00
if _ , loaded := loadedMap [ oldID ] ; loaded {
return nil
}
configPath , err := safePath ( sourceDir , filepath . Join ( oldID , legacyConfigFileName ) )
if err != nil {
return err
}
imageJSON , err := ioutil . ReadFile ( configPath )
if err != nil {
logrus . Debugf ( "Error reading json: %v" , err )
return err
}
var img struct { Parent string }
if err := json . Unmarshal ( imageJSON , & img ) ; err != nil {
return err
}
var parentID image . ID
if img . Parent != "" {
for {
var loaded bool
if parentID , loaded = loadedMap [ img . Parent ] ; ! loaded {
2016-02-03 21:31:47 -05:00
if err := l . legacyLoadImage ( img . Parent , sourceDir , loadedMap , progressOutput ) ; err != nil {
2015-11-18 17:18:07 -05:00
return err
}
} else {
break
}
}
}
// todo: try to connect with migrate code
rootFS := image . NewRootFS ( )
var history [ ] image . History
if parentID != "" {
parentImg , err := l . is . Get ( parentID )
if err != nil {
return err
}
rootFS = parentImg . RootFS
history = parentImg . History
}
layerPath , err := safePath ( sourceDir , filepath . Join ( oldID , legacyLayerFileName ) )
if err != nil {
return err
}
2016-02-03 21:31:47 -05:00
newLayer , err := l . loadLayer ( layerPath , * rootFS , oldID , progressOutput )
2015-11-18 17:18:07 -05:00
if err != nil {
return err
}
rootFS . Append ( newLayer . DiffID ( ) )
h , err := v1 . HistoryFromConfig ( imageJSON , false )
if err != nil {
return err
}
history = append ( history , h )
config , err := v1 . MakeConfigFromV1Config ( imageJSON , rootFS , history )
if err != nil {
return err
}
imgID , err := l . is . Create ( config )
if err != nil {
return err
}
metadata , err := l . ls . Release ( newLayer )
layer . LogReleaseMetadata ( metadata )
if err != nil {
return err
}
if parentID != "" {
if err := l . is . SetParent ( imgID , parentID ) ; err != nil {
return err
}
}
loadedMap [ oldID ] = imgID
return nil
}
func safePath ( base , path string ) ( string , error ) {
return symlink . FollowSymlinkInScope ( filepath . Join ( base , path ) , base )
}