Support symlink layer in image import.
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
parent
304045491c
commit
9f85c48e4c
@ -54,13 +54,29 @@ type manifestDotJSON struct {
|
|||||||
Parent string
|
Parent string
|
||||||
}
|
}
|
||||||
|
|
||||||
// isLayerTar returns true if name is like "deadbeeddeadbeef/layer.tar"
|
// isLayerTar returns true if name is like "foobar/layer.tar"
|
||||||
func isLayerTar(name string) bool {
|
func isLayerTar(name string) bool {
|
||||||
slashes := len(strings.Split(name, "/"))
|
slashes := len(strings.Split(name, "/"))
|
||||||
return slashes == 2 && strings.HasSuffix(name, "/layer.tar")
|
return slashes == 2 && strings.HasSuffix(name, "/layer.tar")
|
||||||
}
|
}
|
||||||
|
|
||||||
// isDotJSON returns true if name is like "deadbeefdeadbeef.json"
|
// followSymlinkLayer returns actual layer name of the symlink layer.
|
||||||
|
// It returns "foobar/layer.tar" if the name is like
|
||||||
|
// "../foobar/layer.tar", and returns error if the name
|
||||||
|
// is not in "../foobar/layer.tar" format.
|
||||||
|
func followSymlinkLayer(name string) (string, error) {
|
||||||
|
parts := strings.Split(name, "/")
|
||||||
|
if len(parts) != 3 || parts[0] != ".." {
|
||||||
|
return "", errors.New("invalid symlink layer")
|
||||||
|
}
|
||||||
|
name = strings.TrimPrefix(name, "../")
|
||||||
|
if !isLayerTar(name) {
|
||||||
|
return "", errors.New("invalid layer tar")
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDotJSON returns true if name is like "foobar.json"
|
||||||
func isDotJSON(name string) bool {
|
func isDotJSON(name string) bool {
|
||||||
slashes := len(strings.Split(name, "/"))
|
slashes := len(strings.Split(name, "/"))
|
||||||
return slashes == 1 && strings.HasSuffix(name, ".json")
|
return slashes == 1 && strings.HasSuffix(name, ".json")
|
||||||
@ -75,7 +91,7 @@ type imageConfig struct {
|
|||||||
// An image MUST have `manifest.json`.
|
// An image MUST have `manifest.json`.
|
||||||
// `repositories` file in Docker Image Spec v1.0 is not supported (yet).
|
// `repositories` file in Docker Image Spec v1.0 is not supported (yet).
|
||||||
// Also, the current implementation assumes the implicit file name convention,
|
// Also, the current implementation assumes the implicit file name convention,
|
||||||
// which is not explicitly documented in the spec. (e.g. deadbeef/layer.tar)
|
// which is not explicitly documented in the spec. (e.g. foobar/layer.tar)
|
||||||
// It returns a group of image references successfully loaded.
|
// It returns a group of image references successfully loaded.
|
||||||
func Import(ctx context.Context, client *containerd.Client, reader io.Reader) (_ []string, retErr error) {
|
func Import(ctx context.Context, client *containerd.Client, reader io.Reader) (_ []string, retErr error) {
|
||||||
ctx, done, err := client.WithLease(ctx)
|
ctx, done, err := client.WithLease(ctx)
|
||||||
@ -98,8 +114,9 @@ func Import(ctx context.Context, client *containerd.Client, reader io.Reader) (_
|
|||||||
tr := tar.NewReader(reader)
|
tr := tar.NewReader(reader)
|
||||||
var (
|
var (
|
||||||
mfsts []manifestDotJSON
|
mfsts []manifestDotJSON
|
||||||
layers = make(map[string]ocispec.Descriptor) // key: filename (deadbeeddeadbeef/layer.tar)
|
symlinkLayers = make(map[string]string) // key: filename (foobar/layer.tar), value: linkname (targetlayerid/layer.tar)
|
||||||
configs = make(map[string]imageConfig) // key: filename (deadbeeddeadbeef.json)
|
layers = make(map[string]ocispec.Descriptor) // key: filename (foobar/layer.tar)
|
||||||
|
configs = make(map[string]imageConfig) // key: filename (foobar.json)
|
||||||
)
|
)
|
||||||
for {
|
for {
|
||||||
hdr, err := tr.Next()
|
hdr, err := tr.Next()
|
||||||
@ -109,6 +126,14 @@ func Import(ctx context.Context, client *containerd.Client, reader io.Reader) (_
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "get next file")
|
return nil, errors.Wrap(err, "get next file")
|
||||||
}
|
}
|
||||||
|
if hdr.Typeflag == tar.TypeSymlink && isLayerTar(hdr.Name) {
|
||||||
|
linkname, err := followSymlinkLayer(hdr.Linkname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "follow symlink layer from %q to %q", hdr.Name, hdr.Linkname)
|
||||||
|
}
|
||||||
|
symlinkLayers[hdr.Name] = linkname
|
||||||
|
continue
|
||||||
|
}
|
||||||
if hdr.Typeflag != tar.TypeReg && hdr.Typeflag != tar.TypeRegA {
|
if hdr.Typeflag != tar.TypeReg && hdr.Typeflag != tar.TypeRegA {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -136,6 +161,13 @@ func Import(ctx context.Context, client *containerd.Client, reader io.Reader) (_
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for name, linkname := range symlinkLayers {
|
||||||
|
desc, ok := layers[linkname]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("no target for symlink layer from %q to %q", name, linkname)
|
||||||
|
}
|
||||||
|
layers[name] = desc
|
||||||
|
}
|
||||||
var refs []string
|
var refs []string
|
||||||
defer func() {
|
defer func() {
|
||||||
if retErr == nil {
|
if retErr == nil {
|
||||||
@ -255,7 +287,7 @@ func onUntarManifestJSON(r io.Reader) ([]manifestDotJSON, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func onUntarLayerTar(ctx context.Context, r io.Reader, cs content.Ingester, name string, size int64) (*ocispec.Descriptor, error) {
|
func onUntarLayerTar(ctx context.Context, r io.Reader, cs content.Ingester, name string, size int64) (*ocispec.Descriptor, error) {
|
||||||
// name is like "deadbeeddeadbeef/layer.tar" ( guaranteed by isLayerTar() )
|
// name is like "foobar/layer.tar" ( guaranteed by isLayerTar() )
|
||||||
split := strings.Split(name, "/")
|
split := strings.Split(name, "/")
|
||||||
// note: split[0] is not expected digest here
|
// note: split[0] is not expected digest here
|
||||||
cw, err := cs.Writer(ctx, "layer-"+split[0], size, "")
|
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
|
|||||||
config := imageConfig{}
|
config := imageConfig{}
|
||||||
config.desc.MediaType = images.MediaTypeDockerSchema2Config
|
config.desc.MediaType = images.MediaTypeDockerSchema2Config
|
||||||
config.desc.Size = size
|
config.desc.Size = size
|
||||||
// name is like "deadbeeddeadbeef.json" ( guaranteed by is DotJSON() )
|
// name is like "foobar.json" ( guaranteed by is DotJSON() )
|
||||||
split := strings.Split(name, ".")
|
split := strings.Split(name, ".")
|
||||||
cw, err := cs.Writer(ctx, "config-"+split[0], size, "")
|
cw, err := cs.Writer(ctx, "config-"+split[0], size, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user