diff --git a/image/image.go b/image/image.go index 9318af1..a92ea23 100644 --- a/image/image.go +++ b/image/image.go @@ -58,6 +58,10 @@ var validRefMediaTypes = []string{ } func validate(w walker, refs []string, out *log.Logger) error { + if err := layoutValidate(w); err != nil { + return err + } + ds, err := listReferences(w) if err != nil { return err @@ -130,6 +134,10 @@ func Unpack(r io.ReadSeeker, dest, refName string) error { } func unpack(w walker, dest, refName string) error { + if err := layoutValidate(w); err != nil { + return err + } + ref, err := findDescriptor(w, refName) if err != nil { return err @@ -178,6 +186,10 @@ func CreateRuntimeBundle(r io.ReadSeeker, dest, ref, root string) error { } func createRuntimeBundle(w walker, dest, refName, rootfs string) error { + if err := layoutValidate(w); err != nil { + return err + } + ref, err := findDescriptor(w, refName) if err != nil { return err diff --git a/image/layout.go b/image/layout.go new file mode 100644 index 0000000..c7eb5e2 --- /dev/null +++ b/image/layout.go @@ -0,0 +1,104 @@ +// 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 ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + + "github.com/opencontainers/image-spec/schema" + "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +func layoutValidate(w walker) error { + var blobsExist, indexExist, layoutExist bool + + if err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { + if strings.EqualFold(path, "blobs") { + blobsExist = true + if !info.IsDir() { + return fmt.Errorf("blobs is not a directory") + } + + return nil + } + + if strings.EqualFold(path, "index.json") { + indexExist = true + if info.IsDir() { + return fmt.Errorf("index.json is a directory") + } + + var index v1.Index + buf, err := ioutil.ReadAll(r) + if err != nil { + return errors.Wrap(err, "error reading index.json") + } + + if err := json.Unmarshal(buf, &index); err != nil { + return errors.Wrap(err, "index.json format mismatch") + } + + return nil + } + + if strings.EqualFold(path, "oci-layout") { + layoutExist = true + if info.IsDir() { + return fmt.Errorf("oci-layout is a directory") + } + + var imageLayout v1.ImageLayout + buf, err := ioutil.ReadAll(r) + if err != nil { + return errors.Wrap(err, "error reading oci-layout") + } + + if err := schema.ValidatorMediaTypeLayoutHeader.Validate(bytes.NewReader(buf)); err != nil { + return errors.Wrap(err, "oci-layout: imageLayout validation failed") + } + + if err := json.Unmarshal(buf, &imageLayout); err != nil { + return errors.Wrap(err, "oci-layout format mismatch") + } + + return nil + } + + return nil + }); err != nil { + return err + } + + if !blobsExist { + return fmt.Errorf("image layout must contain blobs directory") + } + + if !indexExist { + return fmt.Errorf("image layout must contain index.json file") + } + + if !layoutExist { + return fmt.Errorf("image layout must contain oci-layout file") + } + + return nil +}