@@ -54,13 +54,29 @@ type manifestDotJSON struct {
5454 Parent string
5555}
5656
57- // isLayerTar returns true if name is like "deadbeeddeadbeef /layer.tar"
57+ // isLayerTar returns true if name is like "foobar /layer.tar"
5858func isLayerTar (name string ) bool {
5959 slashes := len (strings .Split (name , "/" ))
6060 return slashes == 2 && strings .HasSuffix (name , "/layer.tar" )
6161}
6262
63- // isDotJSON returns true if name is like "deadbeefdeadbeef.json"
63+ // followSymlinkLayer returns actual layer name of the symlink layer.
64+ // It returns "foobar/layer.tar" if the name is like
65+ // "../foobar/layer.tar", and returns error if the name
66+ // is not in "../foobar/layer.tar" format.
67+ func followSymlinkLayer (name string ) (string , error ) {
68+ parts := strings .Split (name , "/" )
69+ if len (parts ) != 3 || parts [0 ] != ".." {
70+ return "" , errors .New ("invalid symlink layer" )
71+ }
72+ name = strings .TrimPrefix (name , "../" )
73+ if ! isLayerTar (name ) {
74+ return "" , errors .New ("invalid layer tar" )
75+ }
76+ return name , nil
77+ }
78+
79+ // isDotJSON returns true if name is like "foobar.json"
6480func isDotJSON (name string ) bool {
6581 slashes := len (strings .Split (name , "/" ))
6682 return slashes == 1 && strings .HasSuffix (name , ".json" )
@@ -75,7 +91,7 @@ type imageConfig struct {
7591// An image MUST have `manifest.json`.
7692// `repositories` file in Docker Image Spec v1.0 is not supported (yet).
7793// Also, the current implementation assumes the implicit file name convention,
78- // which is not explicitly documented in the spec. (e.g. deadbeef /layer.tar)
94+ // which is not explicitly documented in the spec. (e.g. foobar /layer.tar)
7995// It returns a group of image references successfully loaded.
8096func Import (ctx context.Context , client * containerd.Client , reader io.Reader ) (_ []string , retErr error ) {
8197 ctx , done , err := client .WithLease (ctx )
@@ -97,9 +113,10 @@ func Import(ctx context.Context, client *containerd.Client, reader io.Reader) (_
97113
98114 tr := tar .NewReader (reader )
99115 var (
100- mfsts []manifestDotJSON
101- layers = make (map [string ]ocispec.Descriptor ) // key: filename (deadbeeddeadbeef/layer.tar)
102- configs = make (map [string ]imageConfig ) // key: filename (deadbeeddeadbeef.json)
116+ mfsts []manifestDotJSON
117+ symlinkLayers = make (map [string ]string ) // key: filename (foobar/layer.tar), value: linkname (targetlayerid/layer.tar)
118+ layers = make (map [string ]ocispec.Descriptor ) // key: filename (foobar/layer.tar)
119+ configs = make (map [string ]imageConfig ) // key: filename (foobar.json)
103120 )
104121 for {
105122 hdr , err := tr .Next ()
@@ -109,6 +126,14 @@ func Import(ctx context.Context, client *containerd.Client, reader io.Reader) (_
109126 if err != nil {
110127 return nil , errors .Wrap (err , "get next file" )
111128 }
129+ if hdr .Typeflag == tar .TypeSymlink && isLayerTar (hdr .Name ) {
130+ linkname , err := followSymlinkLayer (hdr .Linkname )
131+ if err != nil {
132+ return nil , errors .Wrapf (err , "follow symlink layer from %q to %q" , hdr .Name , hdr .Linkname )
133+ }
134+ symlinkLayers [hdr .Name ] = linkname
135+ continue
136+ }
112137 if hdr .Typeflag != tar .TypeReg && hdr .Typeflag != tar .TypeRegA {
113138 continue
114139 }
@@ -136,6 +161,13 @@ func Import(ctx context.Context, client *containerd.Client, reader io.Reader) (_
136161 continue
137162 }
138163 }
164+ for name , linkname := range symlinkLayers {
165+ desc , ok := layers [linkname ]
166+ if ! ok {
167+ return nil , errors .Errorf ("no target for symlink layer from %q to %q" , name , linkname )
168+ }
169+ layers [name ] = desc
170+ }
139171 var refs []string
140172 defer func () {
141173 if retErr == nil {
@@ -255,7 +287,7 @@ func onUntarManifestJSON(r io.Reader) ([]manifestDotJSON, error) {
255287}
256288
257289func onUntarLayerTar (ctx context.Context , r io.Reader , cs content.Ingester , name string , size int64 ) (* ocispec.Descriptor , error ) {
258- // name is like "deadbeeddeadbeef /layer.tar" ( guaranteed by isLayerTar() )
290+ // name is like "foobar /layer.tar" ( guaranteed by isLayerTar() )
259291 split := strings .Split (name , "/" )
260292 // note: split[0] is not expected digest here
261293 cw , err := cs .Writer (ctx , "layer-" + split [0 ], size , "" )
@@ -277,7 +309,7 @@ func onUntarDotJSON(ctx context.Context, r io.Reader, cs content.Ingester, name
277309 config := imageConfig {}
278310 config .desc .MediaType = images .MediaTypeDockerSchema2Config
279311 config .desc .Size = size
280- // name is like "deadbeeddeadbeef .json" ( guaranteed by is DotJSON() )
312+ // name is like "foobar .json" ( guaranteed by is DotJSON() )
281313 split := strings .Split (name , "." )
282314 cw , err := cs .Writer (ctx , "config-" + split [0 ], size , "" )
283315 if err != nil {
0 commit comments