2018-02-05 16:05:59 -05:00
package tarexport // import "github.com/docker/docker/image/tarexport"
2015-11-18 17:18:07 -05:00
import (
"encoding/json"
2017-04-25 19:45:42 -04:00
"errors"
2015-11-18 17:18:07 -05:00
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
2016-03-21 16:52:36 -04:00
"reflect"
2017-04-25 19:45:42 -04:00
"runtime"
2015-11-18 17:18:07 -05:00
2018-06-27 14:53:20 -04:00
"github.com/containerd/containerd/platforms"
2016-05-25 22:11:51 -04:00
"github.com/docker/distribution"
2017-01-25 19:54:18 -05:00
"github.com/docker/distribution/reference"
2015-11-18 17:18:07 -05:00
"github.com/docker/docker/image"
2019-08-05 10:37:47 -04:00
v1 "github.com/docker/docker/image/v1"
2015-11-18 17:18:07 -05:00
"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"
2016-11-01 18:44:06 -04:00
"github.com/docker/docker/pkg/system"
2020-10-29 19:54:10 -04:00
"github.com/moby/sys/symlink"
2019-08-05 10:37:47 -04:00
digest "github.com/opencontainers/go-digest"
2017-07-26 17:42:13 -04:00
"github.com/sirupsen/logrus"
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 {
2017-05-01 14:54:56 -04:00
var progressOutput progress . Output
2016-02-03 21:31:47 -05:00
if ! quiet {
2017-05-01 14:54:56 -04:00
progressOutput = streamformatter . NewJSONProgressOutput ( outStream , false )
2016-02-03 21:31:47 -05:00
}
2017-05-01 14:54:56 -04:00
outStream = streamformatter . NewStdoutWriter ( outStream )
2016-02-03 21:31:47 -05:00
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
}
2016-08-31 03:37:52 -04:00
return err
2015-11-18 17:18:07 -05:00
}
defer manifestFile . Close ( )
var manifest [ ] manifestItem
if err := json . NewDecoder ( manifestFile ) . Decode ( & manifest ) ; err != nil {
return err
}
2016-03-21 16:52:36 -04:00
var parentLinks [ ] parentLink
2016-05-25 21:25:12 -04:00
var imageIDsStr string
var imageRefCount int
2016-03-21 16:52:36 -04:00
2015-11-18 17:18:07 -05:00
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
}
2017-06-12 21:18:14 -04:00
if err := checkCompatibleOS ( img . OS ) ; err != nil {
return err
}
2017-09-11 14:55:05 -04:00
rootFS := * img . RootFS
2015-11-18 17:18:07 -05:00
rootFS . DiffIDs = nil
if expected , actual := len ( m . Layers ) , len ( img . RootFS . DiffIDs ) ; expected != actual {
2017-01-14 14:03:08 -05:00
return fmt . Errorf ( "invalid manifest, layers length mismatch: expected %d, got %d" , expected , actual )
2015-11-18 17:18:07 -05:00
}
2017-04-25 19:45:42 -04:00
// On Windows, validate the platform, defaulting to windows if not present.
2017-08-24 14:48:16 -04:00
os := img . OS
if os == "" {
os = runtime . GOOS
}
2017-04-25 19:45:42 -04:00
if runtime . GOOS == "windows" {
2017-08-08 15:43:48 -04:00
if ( os != "windows" ) && ( os != "linux" ) {
return fmt . Errorf ( "configuration for this image has an unsupported operating system: %s" , os )
2017-04-25 19:45:42 -04:00
}
}
2015-11-18 17:18:07 -05:00
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 )
2017-09-19 15:14:46 -04:00
newLayer , err := l . lss [ os ] . Get ( r . ChainID ( ) )
2015-11-18 17:18:07 -05:00
if err != nil {
2017-08-08 15:43:48 -04:00
newLayer , err = l . loadLayer ( layerPath , rootFS , diffID . String ( ) , os , m . LayerSources [ diffID ] , progressOutput )
2015-12-16 16:46:40 -05:00
if err != nil {
return err
}
2015-11-18 17:18:07 -05:00
}
2017-09-19 15:14:46 -04:00
defer layer . ReleaseAndLog ( l . lss [ os ] , newLayer )
2015-11-18 17:18:07 -05:00
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
}
2016-05-25 21:25:12 -04:00
imageIDsStr += fmt . Sprintf ( "Loaded image ID: %s\n" , imgID )
2015-11-18 17:18:07 -05:00
2016-05-25 21:25:12 -04:00
imageRefCount = 0
2015-11-18 17:18:07 -05:00
for _ , repoTag := range m . RepoTags {
2017-01-25 19:54:18 -05:00
named , err := reference . ParseNormalizedNamed ( repoTag )
2015-11-18 17:18:07 -05:00
if err != nil {
return err
}
ref , ok := named . ( reference . NamedTagged )
if ! ok {
return fmt . Errorf ( "invalid tag %q" , repoTag )
}
2016-09-15 19:37:32 -04:00
l . setLoadedTag ( ref , imgID . Digest ( ) , outStream )
2017-01-25 19:54:18 -05:00
outStream . Write ( [ ] byte ( fmt . Sprintf ( "Loaded image: %s\n" , reference . FamiliarString ( ref ) ) ) )
2016-05-25 21:25:12 -04:00
imageRefCount ++
2015-11-18 17:18:07 -05:00
}
2016-03-21 16:52:36 -04:00
parentLinks = append ( parentLinks , parentLink { imgID , m . Parent } )
2016-04-19 00:45:59 -04:00
l . loggerImgEvent . LogImageEvent ( imgID . String ( ) , imgID . String ( ) , "load" )
2016-03-21 16:52:36 -04:00
}
for _ , p := range validatedParentLinks ( parentLinks ) {
if p . parentID != "" {
if err := l . setParentID ( p . id , p . parentID ) ; err != nil {
return err
}
}
2015-11-18 17:18:07 -05:00
}
2016-05-25 21:25:12 -04:00
if imageRefCount == 0 {
outStream . Write ( [ ] byte ( imageIDsStr ) )
}
2015-11-18 17:18:07 -05:00
return nil
}
2016-03-21 16:52:36 -04:00
func ( l * tarexporter ) setParentID ( id , parentID image . ID ) error {
img , err := l . is . Get ( id )
if err != nil {
return err
}
parent , err := l . is . Get ( parentID )
if err != nil {
return err
}
if ! checkValidParent ( img , parent ) {
2016-05-02 13:26:32 -04:00
return fmt . Errorf ( "image %v is not a valid parent for %v" , parent . ID ( ) , img . ID ( ) )
2016-03-21 16:52:36 -04:00
}
return l . is . SetParent ( id , parentID )
}
2017-08-24 14:48:16 -04:00
func ( l * tarexporter ) loadLayer ( filename string , rootFS image . RootFS , id string , os string , foreignSrc distribution . Descriptor , progressOutput progress . Output ) ( layer . Layer , error ) {
2016-11-01 18:44:06 -04:00
// We use system.OpenSequential to use sequential file access on Windows, avoiding
// depleting the standby list. On Linux, this equates to a regular os.Open.
rawTar , err := system . OpenSequential ( filename )
2015-11-18 17:18:07 -05:00
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 ( )
2016-06-24 18:15:59 -04:00
var r io . Reader
2016-02-03 21:31:47 -05:00
if progressOutput != nil {
2016-06-24 18:15:59 -04:00
fileInfo , err := rawTar . Stat ( )
2016-02-03 21:31:47 -05:00
if err != nil {
logrus . Debugf ( "Error statting file: %v" , err )
return nil , err
}
2016-06-24 18:15:59 -04:00
r = progress . NewProgressReader ( rawTar , progressOutput , fileInfo . Size ( ) , stringid . TruncateID ( id ) , "Loading layer" )
} else {
r = rawTar
}
2016-06-06 20:49:34 -04:00
2016-06-24 18:15:59 -04:00
inflatedLayerData , err := archive . DecompressStream ( r )
if err != nil {
return nil , err
2016-06-06 20:49:34 -04:00
}
2016-06-24 18:15:59 -04:00
defer inflatedLayerData . Close ( )
2016-06-06 20:49:34 -04:00
2017-09-19 15:14:46 -04:00
if ds , ok := l . lss [ os ] . ( layer . DescribableStore ) ; ok {
return ds . RegisterWithDescriptor ( inflatedLayerData , rootFS . ChainID ( ) , foreignSrc )
2016-02-03 21:31:47 -05:00
}
2017-09-19 15:14:46 -04:00
return l . lss [ os ] . Register ( inflatedLayerData , rootFS . ChainID ( ) )
2015-11-18 17:18:07 -05:00
}
2017-08-23 18:21:41 -04:00
func ( l * tarexporter ) setLoadedTag ( ref reference . Named , imgID digest . Digest , outStream io . Writer ) error {
2015-12-04 16:55:15 -05:00
if prevID , err := l . rs . Get ( ref ) ; err == nil && prevID != imgID {
2017-01-25 19:54:18 -05:00
fmt . Fprintf ( outStream , "The image %s already exists, renaming the old one with ID %s to empty string\n" , reference . FamiliarString ( ref ) , string ( prevID ) ) // todo: this message is wrong in case of multiple tags
2015-11-18 17:18:07 -05:00
}
2017-08-23 18:21:41 -04:00
return l . rs . AddTag ( ref , imgID , true )
2015-11-18 17:18:07 -05:00
}
2016-02-03 21:31:47 -05:00
func ( l * tarexporter ) legacyLoad ( tmpDir string , outStream io . Writer , progressOutput progress . Output ) error {
2017-04-25 19:45:42 -04:00
if runtime . GOOS == "windows" {
return errors . New ( "Windows does not support legacy loading of images" )
}
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 {
2016-08-31 03:37:52 -04:00
return err
2015-11-18 17:18:07 -05:00
}
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 )
}
2017-01-25 19:54:18 -05:00
named , err := reference . ParseNormalizedNamed ( name )
2015-11-18 17:18:07 -05:00
if err != nil {
return err
}
ref , err := reference . WithTag ( named , tag )
if err != nil {
return err
}
2016-09-15 19:37:32 -04:00
l . setLoadedTag ( ref , imgID . Digest ( ) , outStream )
2015-11-18 17:18:07 -05:00
}
}
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
}
2017-06-12 21:18:14 -04:00
var img struct {
OS string
Parent string
}
2015-11-18 17:18:07 -05:00
if err := json . Unmarshal ( imageJSON , & img ) ; err != nil {
return err
}
2017-06-12 21:18:14 -04:00
if err := checkCompatibleOS ( img . OS ) ; err != nil {
return err
}
2017-09-19 15:14:46 -04:00
if img . OS == "" {
img . OS = runtime . GOOS
}
2017-06-12 21:18:14 -04:00
2015-11-18 17:18:07 -05:00
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
}
2017-09-19 15:14:46 -04:00
newLayer , err := l . loadLayer ( layerPath , * rootFS , oldID , img . OS , distribution . Descriptor { } , 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
}
2017-09-19 15:14:46 -04:00
metadata , err := l . lss [ img . OS ] . Release ( newLayer )
2015-11-18 17:18:07 -05:00
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 )
}
2016-03-21 16:52:36 -04:00
type parentLink struct {
id , parentID image . ID
}
func validatedParentLinks ( pl [ ] parentLink ) ( ret [ ] parentLink ) {
mainloop :
for i , p := range pl {
ret = append ( ret , p )
for _ , p2 := range pl {
if p2 . id == p . parentID && p2 . id != p . id {
continue mainloop
}
}
ret [ i ] . parentID = ""
}
return
}
func checkValidParent ( img , parent * image . Image ) bool {
if len ( img . History ) == 0 && len ( parent . History ) == 0 {
return true // having history is not mandatory
}
if len ( img . History ) - len ( parent . History ) != 1 {
return false
}
for i , h := range parent . History {
if ! reflect . DeepEqual ( h , img . History [ i ] ) {
return false
}
}
return true
}
2017-06-12 21:18:14 -04:00
2017-08-24 14:48:16 -04:00
func checkCompatibleOS ( imageOS string ) error {
// always compatible if the images OS matches the host OS; also match an empty image OS
if imageOS == runtime . GOOS || imageOS == "" {
2017-06-12 21:18:14 -04:00
return nil
}
2017-08-24 14:48:16 -04:00
// On non-Windows hosts, for compatibility, fail if the image is Windows.
if runtime . GOOS != "windows" && imageOS == "windows" {
return fmt . Errorf ( "cannot load %s image on %s" , imageOS , runtime . GOOS )
}
2018-06-27 14:53:20 -04:00
p , err := platforms . Parse ( imageOS )
if err != nil {
return err
}
return system . ValidatePlatform ( p )
2017-06-12 21:18:14 -04:00
}