From 4f0b3c1bae97d212680dc73ee8b35b6e47b8a8c6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 28 Sep 2016 10:58:15 -0700 Subject: [PATCH] WIP: validate: Stub in callback-based validation --- cmd/oci-image-validate/main.go | 153 +++++++++------------------------ validate/layer.go | 69 +++++++++++++++ validate/validate.go | 44 ++++++++++ 3 files changed, 154 insertions(+), 112 deletions(-) create mode 100644 validate/layer.go create mode 100644 validate/validate.go diff --git a/cmd/oci-image-validate/main.go b/cmd/oci-image-validate/main.go index ca8e608..266b92f 100644 --- a/cmd/oci-image-validate/main.go +++ b/cmd/oci-image-validate/main.go @@ -16,145 +16,74 @@ package main import ( "fmt" - "log" "os" - "strings" - "github.com/opencontainers/image-spec/schema" - "github.com/opencontainers/image-tools/image" - "github.com/pkg/errors" + "github.com/mndrix/tap.go" + "github.com/opencontainers/image-spec/specs-go" + "github.com/opencontainers/image-tools/image/cas/layout" + "github.com/opencontainers/image-tools/validate" "github.com/spf13/cobra" "golang.org/x/net/context" ) -// supported validation types -var validateTypes = []string{ - image.TypeImage, - image.TypeManifest, - image.TypeManifestList, - image.TypeConfig, -} - type validateCmd struct { - stdout *log.Logger - stderr *log.Logger - typ string // the type to validate, can be empty string - refs []string + mediaType string // the type to validate, can be empty string + digest string + strict bool } func main() { - stdout := log.New(os.Stdout, "", 0) - stderr := log.New(os.Stderr, "", 0) - - cmd := newValidateCmd(stdout, stderr) - if err := cmd.Execute(); err != nil { - stderr.Println(err) - os.Exit(1) - } -} - -func newValidateCmd(stdout, stderr *log.Logger) *cobra.Command { - v := &validateCmd{ - stdout: stdout, - stderr: stderr, - } + validator := &validateCmd{} cmd := &cobra.Command{ - Use: "oci-image-validate FILE...", - Short: "Validate one or more image files", - Run: v.Run, + Use: "oci-image-validate PATH DIGEST", + Short: "Validate an OCI image", + Run: validator.Run, } - cmd.Flags().StringVar( - &v.typ, "type", "", - fmt.Sprintf( - `Type of the file to validate. If unset, oci-image-tool will try to auto-detect the type. One of "%s".`, - strings.Join(validateTypes, ","), - ), - ) - - cmd.Flags().StringSliceVar( - &v.refs, "ref", nil, - `A set of refs pointing to the manifests to be validated. Each reference must be present in the "refs" subdirectory of the image. Only applicable if type is image.`, - ) + err := cmd.Execute() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } - return cmd + os.Exit(0) } -func (v *validateCmd) Run(cmd *cobra.Command, args []string) { - if len(args) < 1 { - v.stderr.Printf("no files specified") - if err := cmd.Usage(); err != nil { - v.stderr.Println(err) +func (validator *validateCmd) Run(cmd *cobra.Command, args []string) { + if len(args) != 2 { + err := cmd.Usage() + if err != nil { + fmt.Fprintln(os.Stderr, err) } os.Exit(1) } - ctx := context.Background() - - var exitcode int - for _, arg := range args { - err := v.validatePath(ctx, arg) - - if err == nil { - v.stdout.Printf("%s: OK", arg) - continue - } - - var errs []error - if verr, ok := errors.Cause(err).(schema.ValidationError); ok { - errs = verr.Errs - } else if serr, ok := errors.Cause(err).(*schema.SyntaxError); ok { - v.stderr.Printf("%s:%d:%d: validation failed: %v", arg, serr.Line, serr.Col, err) - exitcode = 1 - continue - } else { - v.stderr.Printf("%s: validation failed: %v", arg, err) - exitcode = 1 - continue - } + path := args[0] + validator.mediaType = "application/vnd.oci.image.layer.tar+gzip" + validator.digest = args[1] + validator.strict = true - for _, err := range errs { - v.stderr.Printf("%s: validation failed: %v", arg, err) - } + ctx := context.Background() - exitcode = 1 + engine, err := layout.NewEngine(ctx, path) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) } + defer engine.Close() - os.Exit(exitcode) -} + harness := tap.New() + harness.Header(0) -func (v *validateCmd) validatePath(ctx context.Context, name string) error { - var ( - err error - typ = v.typ - ) - - if typ == "" { - if typ, err = image.Autodetect(name); err != nil { - return errors.Wrap(err, "unable to determine type") - } - } - - switch typ { - case image.TypeImage: - return image.Validate(ctx, name, v.refs, v.stdout) + descriptor := &specs.Descriptor{ + MediaType: validator.mediaType, + Digest: validator.digest, + Size: -1, } + validate.Validate(ctx, harness, engine, descriptor, validator.strict) - f, err := os.Open(name) - if err != nil { - return errors.Wrap(err, "unable to open file") - } - defer f.Close() - - switch typ { - case image.TypeManifest: - return schema.MediaTypeManifest.Validate(f) - case image.TypeManifestList: - return schema.MediaTypeManifestList.Validate(f) - case image.TypeConfig: - return schema.MediaTypeImageConfig.Validate(f) - } + harness.AutoPlan() - return fmt.Errorf("type %q unimplemented", typ) + return } diff --git a/validate/layer.go b/validate/layer.go new file mode 100644 index 0000000..ae98400 --- /dev/null +++ b/validate/layer.go @@ -0,0 +1,69 @@ +// 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 validate provides a framework for validating OCI images. +package validate + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" + + "github.com/mndrix/tap.go" + "github.com/opencontainers/image-tools/image/cas" + "golang.org/x/net/context" +) + +func ValidateGzippedLayer(ctx context.Context, harness *tap.T, engine cas.Engine, digest string, strict bool) { + reader, err := engine.Get(ctx, digest) + harness.Ok(err == nil, fmt.Sprintf("retrieve %s from CAS", digest)) + if err != nil { + return + } + + gzipReader, err := gzip.NewReader(reader) + harness.Ok(err == nil, fmt.Sprintf("gzip reader for %s", digest)) + if err != nil { + return + } + + tarReader := tar.NewReader(gzipReader) + for { + select { + case <-ctx.Done(): + harness.Ok(false, ctx.Err().Error()) + return + default: + } + + header, err := tarReader.Next() + if err == io.EOF { + return + } + harness.Ok(err == nil, fmt.Sprintf("read tar header from %s", digest)) + if err != nil { + return + } + + message := fmt.Sprintf("ustar typeflag in %s (%q)", digest, header.Typeflag) + if header.Typeflag < tar.TypeReg || header.Typeflag > tar.TypeFifo { + harness.Ok(!strict, message) + } else { + harness.Ok(true, message) + } + } + + return +} diff --git a/validate/validate.go b/validate/validate.go new file mode 100644 index 0000000..0444cc8 --- /dev/null +++ b/validate/validate.go @@ -0,0 +1,44 @@ +// 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 validate provides a framework for validating OCI images. +package validate + +import ( + "fmt" + + "github.com/mndrix/tap.go" + "github.com/opencontainers/image-spec/specs-go" + "github.com/opencontainers/image-tools/image/cas" + "golang.org/x/net/context" +) + +// Validate is a template for validating a CAS object. +type Validator func(ctx context.Context, harness *tap.T, engine cas.Engine, digest string, strict bool) + +// Validators is a map from media types to an appropriate Validator function. +var Validators = map[string]Validator{ + "application/vnd.oci.image.layer.tar+gzip": ValidateGzippedLayer, + "application/vnd.oci.image.layer.nondistributable.tar+gzip": ValidateGzippedLayer, + // TODO: fill in other types +} + +func Validate(ctx context.Context, harness *tap.T, engine cas.Engine, descriptor *specs.Descriptor, strict bool) { + validator, ok := Validators[descriptor.MediaType] + harness.Ok(ok, fmt.Sprintf("recognized media type %q", descriptor.MediaType)) + if !ok { + return + } + validator(ctx, harness, engine, descriptor.Digest, strict) +}