Skip to content
This repository was archived by the owner on Mar 9, 2022. It is now read-only.

Commit b09489d

Browse files
authored
Merge pull request #727 from Random-Liu/fix-symlink-layer
Support symlink layer in image import.
2 parents de0afd7 + 9f85c48 commit b09489d

File tree

1 file changed

+40
-8
lines changed

1 file changed

+40
-8
lines changed

pkg/containerd/importer/importer.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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"
5858
func 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"
6480
func 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.
8096
func 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

257289
func 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

Comments
 (0)