From f2dc0e2d92eb0b39adff14601d84606d894bed16 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 17 Jun 2016 07:04:08 -0700 Subject: [PATCH] image: Refactor to use cas/ref engines instead of walkers The validation/unpacking code doesn't really care what the reference and CAS implemenations are. And the new generic interfaces in image/refs and image/cas will scale better as we add new backends than the walker interface. The old tar/directory distinction between image and imageLayout is gone. The new CAS/refs engines don't support directory backends yet (I plan on adding them once the engine framework lands), but the new framework will handle tar/directory/... detection inside layout.NewEngine (and possibly inside a new (cas|refs).NewEngine when we grow engine types that aren't based on image-layout). I'd prefer casLayout and refsLayout for the imported packages, but Stephen doesn't want camelCase for package names [1]. [1]: https://github.com/opencontainers/image-spec/pull/159#discussion_r76720225 Signed-off-by: W. Trevor King --- cmd/oci-image-tool/autodetect.go | 3 +- cmd/oci-image-tool/create_runtime_bundle.go | 9 +- .../oci-image-tool-create-runtime-bundle.1.md | 2 +- .../man/oci-image-tool-unpack.1.md | 2 +- .../man/oci-image-tool-validate.1.md | 6 +- cmd/oci-image-tool/unpack.go | 13 +- cmd/oci-image-tool/validate.go | 14 +- image/config.go | 60 ++++----- image/descriptor.go | 88 ++----------- image/image.go | 123 +++++++++--------- image/manifest.go | 92 +++++-------- image/walker.go | 112 ---------------- 12 files changed, 157 insertions(+), 367 deletions(-) delete mode 100644 image/walker.go diff --git a/cmd/oci-image-tool/autodetect.go b/cmd/oci-image-tool/autodetect.go index 094e7b9ca..d71b657d0 100644 --- a/cmd/oci-image-tool/autodetect.go +++ b/cmd/oci-image-tool/autodetect.go @@ -27,7 +27,6 @@ import ( // supported autodetection types const ( - typeImageLayout = "imageLayout" typeImage = "image" typeManifest = "manifest" typeManifestList = "manifestList" @@ -43,7 +42,7 @@ func autodetect(path string) (string, error) { } if fi.IsDir() { - return typeImageLayout, nil + return typeImage, nil } f, err := os.Open(path) diff --git a/cmd/oci-image-tool/create_runtime_bundle.go b/cmd/oci-image-tool/create_runtime_bundle.go index 57b6745bf..ec07c0acf 100644 --- a/cmd/oci-image-tool/create_runtime_bundle.go +++ b/cmd/oci-image-tool/create_runtime_bundle.go @@ -22,11 +22,11 @@ import ( "github.com/opencontainers/image-spec/image" "github.com/spf13/cobra" + "golang.org/x/net/context" ) // supported bundle types var bundleTypes = []string{ - typeImageLayout, typeImage, } @@ -82,6 +82,8 @@ func (v *bundleCmd) Run(cmd *cobra.Command, args []string) { os.Exit(1) } + ctx := context.Background() + if _, err := os.Stat(args[1]); os.IsNotExist(err) { v.stderr.Printf("destination path %s does not exist", args[1]) os.Exit(1) @@ -98,11 +100,8 @@ func (v *bundleCmd) Run(cmd *cobra.Command, args []string) { var err error switch v.typ { - case typeImageLayout: - err = image.CreateRuntimeBundleLayout(args[0], args[1], v.ref, v.root) - case typeImage: - err = image.CreateRuntimeBundle(args[0], args[1], v.ref, v.root) + err = image.CreateRuntimeBundle(ctx, args[0], args[1], v.ref, v.root) } if err != nil { diff --git a/cmd/oci-image-tool/man/oci-image-tool-create-runtime-bundle.1.md b/cmd/oci-image-tool/man/oci-image-tool-create-runtime-bundle.1.md index d033bfb06..41f40751b 100644 --- a/cmd/oci-image-tool/man/oci-image-tool-create-runtime-bundle.1.md +++ b/cmd/oci-image-tool/man/oci-image-tool-create-runtime-bundle.1.md @@ -22,7 +22,7 @@ oci-image-tool-create-runtime-bundle \- Create an OCI image runtime bundle A directory representing the root filesystem of the container in the OCI runtime bundle. It is strongly recommended to keep the default value. (default "rootfs") **--type** - Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image" + Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "image" # EXAMPLES ``` diff --git a/cmd/oci-image-tool/man/oci-image-tool-unpack.1.md b/cmd/oci-image-tool/man/oci-image-tool-unpack.1.md index b73e21829..ac6134ebd 100644 --- a/cmd/oci-image-tool/man/oci-image-tool-unpack.1.md +++ b/cmd/oci-image-tool/man/oci-image-tool-unpack.1.md @@ -19,7 +19,7 @@ oci-image-tool-unpack \- Unpack an image or image source layout The ref pointing to the manifest to be unpacked. This must be present in the "refs" subdirectory of the image. (default "v1.0") **--type** - Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image" + Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "image" # EXAMPLES ``` diff --git a/cmd/oci-image-tool/man/oci-image-tool-validate.1.md b/cmd/oci-image-tool/man/oci-image-tool-validate.1.md index 117ef9b2d..832cefbd5 100644 --- a/cmd/oci-image-tool/man/oci-image-tool-validate.1.md +++ b/cmd/oci-image-tool/man/oci-image-tool-validate.1.md @@ -16,15 +16,15 @@ oci-image-tool-validate \- Validate one or more image files Print usage statement **--ref** - The ref pointing to the manifest to be validated. This must be present in the "refs" subdirectory of the image. Only applicable if type is image or imageLayout. (default "v1.0") + The ref pointing to the manifest to be validated. This must be present in the "refs" subdirectory of the image. Only applicable if type is image. (default "v1.0") **--type** - Type of the file to validate. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image,manifest,manifestList,config" + Type of the file to validate. If unset, oci-image-tool will try to auto-detect the type. One of "image,manifest,manifestList,config" # EXAMPLES ``` $ skopeo copy docker://busybox oci:busybox-oci -$ oci-image-tool validate --type imageLayout --ref latest busybox-oci +$ oci-image-tool validate --type image --ref latest busybox-oci busybox-oci: OK ``` diff --git a/cmd/oci-image-tool/unpack.go b/cmd/oci-image-tool/unpack.go index e02ad706d..d2d3c7f67 100644 --- a/cmd/oci-image-tool/unpack.go +++ b/cmd/oci-image-tool/unpack.go @@ -22,11 +22,11 @@ import ( "github.com/opencontainers/image-spec/image" "github.com/spf13/cobra" + "golang.org/x/net/context" ) // supported unpack types var unpackTypes = []string{ - typeImageLayout, typeImage, } @@ -45,8 +45,8 @@ func newUnpackCmd(stdout, stderr *log.Logger) *cobra.Command { cmd := &cobra.Command{ Use: "unpack [src] [dest]", - Short: "Unpack an image or image source layout", - Long: `Unpack the OCI image .tar file or OCI image layout directory present at [src] to the destination directory [dest].`, + Short: "Unpack an image", + Long: `Unpack the OCI image present at [src] to the destination directory [dest].`, Run: v.Run, } @@ -75,6 +75,8 @@ func (v *unpackCmd) Run(cmd *cobra.Command, args []string) { os.Exit(1) } + ctx := context.Background() + if v.typ == "" { typ, err := autodetect(args[0]) if err != nil { @@ -86,11 +88,8 @@ func (v *unpackCmd) Run(cmd *cobra.Command, args []string) { var err error switch v.typ { - case typeImageLayout: - err = image.UnpackLayout(args[0], args[1], v.ref) - case typeImage: - err = image.Unpack(args[0], args[1], v.ref) + err = image.Unpack(ctx, args[0], args[1], v.ref) } if err != nil { diff --git a/cmd/oci-image-tool/validate.go b/cmd/oci-image-tool/validate.go index c2b42e2ef..8171e4e60 100644 --- a/cmd/oci-image-tool/validate.go +++ b/cmd/oci-image-tool/validate.go @@ -24,11 +24,11 @@ import ( "github.com/opencontainers/image-spec/schema" "github.com/pkg/errors" "github.com/spf13/cobra" + "golang.org/x/net/context" ) // supported validation types var validateTypes = []string{ - typeImageLayout, typeImage, typeManifest, typeManifestList, @@ -64,7 +64,7 @@ func newValidateCmd(stdout, stderr *log.Logger) *cobra.Command { cmd.Flags().StringVar( &v.ref, "ref", "v1.0", - `The ref pointing to the manifest to be validated. This must be present in the "refs" subdirectory of the image. Only applicable if type is image or imageLayout.`, + `The ref pointing to the manifest to be validated. This must be present in the "refs" subdirectory of the image. Only applicable if type is image.`, ) return cmd @@ -79,9 +79,11 @@ func (v *validateCmd) Run(cmd *cobra.Command, args []string) { os.Exit(1) } + ctx := context.Background() + var exitcode int for _, arg := range args { - err := v.validatePath(arg) + err := v.validatePath(ctx, arg) if err == nil { v.stdout.Printf("%s: OK", arg) @@ -111,7 +113,7 @@ func (v *validateCmd) Run(cmd *cobra.Command, args []string) { os.Exit(exitcode) } -func (v *validateCmd) validatePath(name string) error { +func (v *validateCmd) validatePath(ctx context.Context, name string) error { var err error typ := v.typ @@ -122,10 +124,8 @@ func (v *validateCmd) validatePath(name string) error { } switch typ { - case typeImageLayout: - return image.ValidateLayout(name, v.ref) case typeImage: - return image.Validate(name, v.ref) + return image.Validate(ctx, name, v.ref) } f, err := os.Open(name) diff --git a/image/config.go b/image/config.go index 67001ce40..885c88211 100644 --- a/image/config.go +++ b/image/config.go @@ -18,16 +18,16 @@ import ( "bytes" "encoding/json" "fmt" - "io" "io/ioutil" - "os" - "path/filepath" "strconv" "strings" + "github.com/opencontainers/image-spec/image/cas" "github.com/opencontainers/image-spec/schema" - "github.com/opencontainers/runtime-spec/specs-go" + imageSpecs "github.com/opencontainers/image-spec/specs-go" + runtimeSpecs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" + "golang.org/x/net/context" ) type cfg struct { @@ -49,43 +49,35 @@ type config struct { Config cfg `json:"config"` } -func findConfig(w walker, d *descriptor) (*config, error) { - var c config - cpath := filepath.Join("blobs", d.algo(), d.hash()) +func findConfig(ctx context.Context, engine cas.Engine, descriptor *imageSpecs.Descriptor) (*config, error) { + reader, err := engine.Get(ctx, descriptor.Digest) + if err != nil { + return nil, err + } - switch err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { - if info.IsDir() || filepath.Clean(path) != cpath { - return nil - } - buf, err := ioutil.ReadAll(r) - if err != nil { - return errors.Wrapf(err, "%s: error reading config", path) - } + buf, err := ioutil.ReadAll(reader) + if err != nil { + return nil, errors.Wrapf(err, "%s: error reading manifest", descriptor.Digest) + } - if err := schema.MediaTypeImageConfig.Validate(bytes.NewReader(buf)); err != nil { - return errors.Wrapf(err, "%s: config validation failed", path) - } + if err := schema.MediaTypeImageConfig.Validate(bytes.NewReader(buf)); err != nil { + return nil, errors.Wrapf(err, "%s: config validation failed", descriptor.Digest) + } - if err := json.Unmarshal(buf, &c); err != nil { - return err - } - return errEOW - }); err { - case nil: - return nil, fmt.Errorf("%s: config not found", cpath) - case errEOW: - return &c, nil - default: + var c config + if err := json.Unmarshal(buf, &c); err != nil { return nil, err } + + return &c, nil } -func (c *config) runtimeSpec(rootfs string) (*specs.Spec, error) { +func (c *config) runtimeSpec(rootfs string) (*runtimeSpecs.Spec, error) { if c.OS != "linux" { return nil, fmt.Errorf("%s: unsupported OS", c.OS) } - var s specs.Spec + var s runtimeSpecs.Spec s.Version = "0.5.0" // we should at least apply the default spec, otherwise this is totally useless s.Process.Terminal = true @@ -128,12 +120,12 @@ func (c *config) runtimeSpec(rootfs string) (*specs.Spec, error) { swap := uint64(c.Config.MemorySwap) shares := uint64(c.Config.CPUShares) - s.Linux.Resources = &specs.Resources{ - CPU: &specs.CPU{ + s.Linux.Resources = &runtimeSpecs.Resources{ + CPU: &runtimeSpecs.CPU{ Shares: &shares, }, - Memory: &specs.Memory{ + Memory: &runtimeSpecs.Memory{ Limit: &mem, Reservation: &mem, Swap: &swap, @@ -143,7 +135,7 @@ func (c *config) runtimeSpec(rootfs string) (*specs.Spec, error) { for vol := range c.Config.Volumes { s.Mounts = append( s.Mounts, - specs.Mount{ + runtimeSpecs.Mount{ Destination: vol, Type: "bind", Options: []string{"rbind"}, diff --git a/image/descriptor.go b/image/descriptor.go index 106ab7fd9..1494af8d4 100644 --- a/image/descriptor.go +++ b/image/descriptor.go @@ -17,88 +17,24 @@ package image import ( "crypto/sha256" "encoding/hex" - "encoding/json" - "fmt" "io" - "os" - "path/filepath" - "strings" + "github.com/opencontainers/image-spec/image/cas" + "github.com/opencontainers/image-spec/specs-go" "github.com/pkg/errors" + "golang.org/x/net/context" ) -type descriptor struct { - MediaType string `json:"mediaType"` - Digest string `json:"digest"` - Size int64 `json:"size"` -} - -func (d *descriptor) algo() string { - pts := strings.SplitN(d.Digest, ":", 2) - if len(pts) != 2 { - return "" - } - return pts[0] -} - -func (d *descriptor) hash() string { - pts := strings.SplitN(d.Digest, ":", 2) - if len(pts) != 2 { - return "" - } - return pts[1] -} - -func findDescriptor(w walker, name string) (*descriptor, error) { - var d descriptor - dpath := filepath.Join("refs", name) - - switch err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { - if info.IsDir() || filepath.Clean(path) != dpath { - return nil - } - - if err := json.NewDecoder(r).Decode(&d); err != nil { - return err - } - - return errEOW - }); err { - case nil: - return nil, fmt.Errorf("%s: descriptor not found", dpath) - case errEOW: - return &d, nil - default: - return nil, err +func validateDescriptor(ctx context.Context, engine cas.Engine, descriptor *specs.Descriptor) error { + reader, err := engine.Get(ctx, descriptor.Digest) + if err != nil { + return err } -} -func (d *descriptor) validate(w walker) error { - switch err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { - if info.IsDir() { - return nil - } - - filename, err := filepath.Rel(filepath.Join("blobs", d.algo()), filepath.Clean(path)) - if err != nil || d.hash() != filename { - return nil - } - - if err := d.validateContent(r); err != nil { - return err - } - return errEOW - }); err { - case nil: - return fmt.Errorf("%s: not found", d.Digest) - case errEOW: - return nil - default: - return errors.Wrapf(err, "%s: validation failed", d.Digest) - } + return validateContent(ctx, descriptor, reader) } -func (d *descriptor) validateContent(r io.Reader) error { +func validateContent(ctx context.Context, descriptor *specs.Descriptor, r io.Reader) error { h := sha256.New() n, err := io.Copy(h, r) if err != nil { @@ -107,13 +43,15 @@ func (d *descriptor) validateContent(r io.Reader) error { digest := "sha256:" + hex.EncodeToString(h.Sum(nil)) - if digest != d.Digest { + if digest != descriptor.Digest { return errors.New("digest mismatch") } - if n != d.Size { + if n != descriptor.Size { return errors.New("size mismatch") } + // FIXME: check descriptor.MediaType, when possible + return nil } diff --git a/image/image.go b/image/image.go index 04ce278ee..18723ad09 100644 --- a/image/image.go +++ b/image/image.go @@ -19,136 +19,133 @@ import ( "os" "path/filepath" - "github.com/pkg/errors" + "github.com/opencontainers/image-spec/image/cas" + caslayout "github.com/opencontainers/image-spec/image/cas/layout" + "github.com/opencontainers/image-spec/image/refs" + refslayout "github.com/opencontainers/image-spec/image/refs/layout" + "golang.org/x/net/context" ) -// ValidateLayout walks through the file tree given by src and -// validates the manifest pointed to by the given ref -// or returns an error if the validation failed. -func ValidateLayout(src, ref string) error { - return validate(newPathWalker(src), ref) -} +// Validate validates the given reference. +func Validate(ctx context.Context, path, ref string) error { + refEngine, err := refslayout.NewEngine(path) + if err != nil { + return err + } + defer refEngine.Close() -// Validate walks through the given .tar file and -// validates the manifest pointed to by the given ref -// or returns an error if the validation failed. -func Validate(tarFile, ref string) error { - f, err := os.Open(tarFile) + casEngine, err := caslayout.NewEngine(path) if err != nil { - return errors.Wrap(err, "unable to open file") + return err } - defer f.Close() + defer casEngine.Close() - return validate(newTarWalker(f), ref) + return validate(ctx, refEngine, casEngine, ref) } -func validate(w walker, refName string) error { - ref, err := findDescriptor(w, refName) +func validate(ctx context.Context, refEngine refs.Engine, casEngine cas.Engine, ref string) error { + descriptor, err := refEngine.Get(ctx, ref) if err != nil { return err } - if err = ref.validate(w); err != nil { + err = validateDescriptor(ctx, casEngine, descriptor) + if err != nil { return err } - m, err := findManifest(w, ref) + m, err := findManifest(ctx, casEngine, descriptor) if err != nil { return err } - return m.validate(w) + return m.validate(ctx, casEngine) } -// UnpackLayout walks through the file tree given by src and -// using the layers specified in the manifest pointed to by the given ref -// and unpacks all layers in the given destination directory -// or returns an error if the unpacking failed. -func UnpackLayout(src, dest, ref string) error { - return unpack(newPathWalker(src), dest, ref) -} +// Unpack unpacks the given reference to a destination directory. +func Unpack(ctx context.Context, path, dest, ref string) error { + refEngine, err := refslayout.NewEngine(path) + if err != nil { + return err + } + defer refEngine.Close() -// Unpack walks through the given .tar file and -// using the layers specified in the manifest pointed to by the given ref -// and unpacks all layers in the given destination directory -// or returns an error if the unpacking failed. -func Unpack(tarFile, dest, ref string) error { - f, err := os.Open(tarFile) + casEngine, err := caslayout.NewEngine(path) if err != nil { - return errors.Wrap(err, "unable to open file") + return err } - defer f.Close() + defer casEngine.Close() - return unpack(newTarWalker(f), dest, ref) + return unpack(ctx, refEngine, casEngine, dest, ref) } -func unpack(w walker, dest, refName string) error { - ref, err := findDescriptor(w, refName) +func unpack(ctx context.Context, refEngine refs.Engine, casEngine cas.Engine, dest, ref string) error { + descriptor, err := refEngine.Get(ctx, ref) if err != nil { return err } - if err = ref.validate(w); err != nil { + err = validateDescriptor(ctx, casEngine, descriptor) + if err != nil { return err } - m, err := findManifest(w, ref) + m, err := findManifest(ctx, casEngine, descriptor) if err != nil { return err } - if err = m.validate(w); err != nil { + if err = m.validate(ctx, casEngine); err != nil { return err } - return m.unpack(w, dest) + return m.unpack(ctx, casEngine, dest) } -// CreateRuntimeBundleLayout walks through the file tree given by src and -// creates an OCI runtime bundle in the given destination dest -// or returns an error if the unpacking failed. -func CreateRuntimeBundleLayout(src, dest, ref, root string) error { - return createRuntimeBundle(newPathWalker(src), dest, ref, root) -} +// CreateRuntimeBundle creates an OCI runtime bundle in the given +// destination. +func CreateRuntimeBundle(ctx context.Context, path, dest, ref, rootfs string) error { + refEngine, err := refslayout.NewEngine(path) + if err != nil { + return err + } + defer refEngine.Close() -// CreateRuntimeBundle walks through the given .tar file and -// creates an OCI runtime bundle in the given destination dest -// or returns an error if the unpacking failed. -func CreateRuntimeBundle(tarFile, dest, ref, root string) error { - f, err := os.Open(tarFile) + casEngine, err := caslayout.NewEngine(path) if err != nil { - return errors.Wrap(err, "unable to open file") + return err } - defer f.Close() + defer casEngine.Close() - return createRuntimeBundle(newTarWalker(f), dest, ref, root) + return createRuntimeBundle(ctx, refEngine, casEngine, dest, ref, rootfs) } -func createRuntimeBundle(w walker, dest, refName, rootfs string) error { - ref, err := findDescriptor(w, refName) +func createRuntimeBundle(ctx context.Context, refEngine refs.Engine, casEngine cas.Engine, dest, ref, rootfs string) error { + descriptor, err := refEngine.Get(ctx, ref) if err != nil { return err } - if err = ref.validate(w); err != nil { + err = validateDescriptor(ctx, casEngine, descriptor) + if err != nil { return err } - m, err := findManifest(w, ref) + m, err := findManifest(ctx, casEngine, descriptor) if err != nil { return err } - if err = m.validate(w); err != nil { + if err = m.validate(ctx, casEngine); err != nil { return err } - c, err := findConfig(w, &m.Config) + c, err := findConfig(ctx, casEngine, m.Config) if err != nil { return err } - err = m.unpack(w, filepath.Join(dest, rootfs)) + err = m.unpack(ctx, casEngine, filepath.Join(dest, rootfs)) if err != nil { return err } diff --git a/image/manifest.go b/image/manifest.go index 8bac949c8..703c15641 100644 --- a/image/manifest.go +++ b/image/manifest.go @@ -27,59 +27,52 @@ import ( "strings" "time" + "github.com/opencontainers/image-spec/image/cas" "github.com/opencontainers/image-spec/schema" + "github.com/opencontainers/image-spec/specs-go" "github.com/pkg/errors" + "golang.org/x/net/context" ) type manifest struct { - Config descriptor `json:"config"` - Layers []descriptor `json:"layers"` + Config *specs.Descriptor `json:"config"` + Layers []specs.Descriptor `json:"layers"` } -func findManifest(w walker, d *descriptor) (*manifest, error) { - var m manifest - mpath := filepath.Join("blobs", d.algo(), d.hash()) - - switch err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { - if info.IsDir() || filepath.Clean(path) != mpath { - return nil - } - - buf, err := ioutil.ReadAll(r) - if err != nil { - return errors.Wrapf(err, "%s: error reading manifest", path) - } - - if err := schema.MediaTypeManifest.Validate(bytes.NewReader(buf)); err != nil { - return errors.Wrapf(err, "%s: manifest validation failed", path) - } +func findManifest(ctx context.Context, engine cas.Engine, descriptor *specs.Descriptor) (*manifest, error) { + reader, err := engine.Get(ctx, descriptor.Digest) + if err != nil { + return nil, err + } - if err := json.Unmarshal(buf, &m); err != nil { - return err - } + buf, err := ioutil.ReadAll(reader) + if err != nil { + return nil, errors.Wrapf(err, "%s: error reading manifest", descriptor.Digest) + } - if len(m.Layers) == 0 { - return fmt.Errorf("%s: no layers found", path) - } + if err := schema.MediaTypeManifest.Validate(bytes.NewReader(buf)); err != nil { + return nil, errors.Wrapf(err, "%s: manifest validation failed", descriptor.Digest) + } - return errEOW - }); err { - case nil: - return nil, fmt.Errorf("%s: manifest not found", mpath) - case errEOW: - return &m, nil - default: + var m manifest + if err := json.Unmarshal(buf, &m); err != nil { return nil, err } + + if len(m.Layers) == 0 { + return nil, fmt.Errorf("%s: no layers found", descriptor.Digest) + } + + return &m, nil } -func (m *manifest) validate(w walker) error { - if err := m.Config.validate(w); err != nil { +func (m *manifest) validate(ctx context.Context, engine cas.Engine) error { + if err := validateDescriptor(ctx, engine, m.Config); err != nil { return errors.Wrap(err, "config validation failed") } for _, d := range m.Layers { - if err := d.validate(w); err != nil { + if err := validateDescriptor(ctx, engine, &d); err != nil { return errors.Wrap(err, "layer validation failed") } } @@ -87,35 +80,20 @@ func (m *manifest) validate(w walker) error { return nil } -func (m *manifest) unpack(w walker, dest string) error { +func (m *manifest) unpack(ctx context.Context, engine cas.Engine, dest string) error { for _, d := range m.Layers { if d.MediaType != string(schema.MediaTypeImageConfig) { continue } - switch err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { - if info.IsDir() { - return nil - } - - dd, err := filepath.Rel(filepath.Join("blobs", d.algo()), filepath.Clean(path)) - if err != nil || d.hash() != dd { - return nil - } - - if err := unpackLayer(dest, r); err != nil { - return errors.Wrap(err, "error extracting layer") - } - - return errEOW - }); err { - case nil: - return fmt.Errorf("%s: layer not found", dest) - case errEOW: - return nil - default: + reader, err := engine.Get(ctx, d.Digest) + if err != nil { return err } + + if err := unpackLayer(dest, reader); err != nil { + return errors.Wrap(err, "error extracting layer") + } } return nil } diff --git a/image/walker.go b/image/walker.go deleted file mode 100644 index 777bce7d5..000000000 --- a/image/walker.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2016 The Linux Foundation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package image - -import ( - "archive/tar" - "fmt" - "io" - "os" - "path/filepath" - - "github.com/pkg/errors" -) - -var ( - errEOW = fmt.Errorf("end of walk") // error to signal stop walking -) - -// walkFunc is a function type that gets called for each file or directory visited by the Walker. -type walkFunc func(path string, _ os.FileInfo, _ io.Reader) error - -// walker is the interface that walks through a file tree, -// calling walk for each file or directory in the tree. -type walker interface { - walk(walkFunc) error -} - -type tarWalker struct { - r io.ReadSeeker -} - -// newTarWalker returns a Walker that walks through .tar files. -func newTarWalker(r io.ReadSeeker) walker { - return &tarWalker{r} -} - -func (w *tarWalker) walk(f walkFunc) error { - if _, err := w.r.Seek(0, os.SEEK_SET); err != nil { - return errors.Wrapf(err, "unable to reset") - } - - tr := tar.NewReader(w.r) - -loop: - for { - hdr, err := tr.Next() - switch err { - case io.EOF: - break loop - case nil: - // success, continue below - default: - return errors.Wrapf(err, "error advancing tar stream") - } - - info := hdr.FileInfo() - if err := f(hdr.Name, info, tr); err != nil { - return err - } - } - - return nil -} - -type eofReader struct{} - -func (eofReader) Read(_ []byte) (int, error) { - return 0, io.EOF -} - -type pathWalker struct { - root string -} - -// newPathWalker returns a Walker that walks through directories -// starting at the given root path. It does not follow symlinks. -func newPathWalker(root string) walker { - return &pathWalker{root} -} - -func (w *pathWalker) walk(f walkFunc) error { - return filepath.Walk(w.root, func(path string, info os.FileInfo, err error) error { - rel, err := filepath.Rel(w.root, path) - if err != nil { - return errors.Wrap(err, "error walking path") // err from filepath.Walk includes path name - } - - if info.IsDir() { // behave like a tar reader for directories - return f(rel, info, eofReader{}) - } - - file, err := os.Open(path) - if err != nil { - return errors.Wrap(err, "unable to open file") // os.Open includes the path - } - defer file.Close() - - return f(rel, info, file) - }) -}