From c3ac69cefe8f687adfa9684030e8391928584fe7 Mon Sep 17 00:00:00 2001 From: zhouhao Date: Mon, 14 Aug 2017 18:25:30 +0800 Subject: [PATCH] Modify the ref optiont Signed-off-by: zhouhao --- cmd/oci-image-tool/create.go | 19 ++++--- cmd/oci-image-tool/unpack.go | 19 ++++--- cmd/oci-image-tool/validate.go | 4 +- image/descriptor.go | 68 ++++++++++++++++-------- image/image.go | 91 ++++++++++++++++---------------- image/image_test.go | 23 ++++---- man/oci-image-tool-create.1.md | 9 ++-- man/oci-image-tool-unpack.1.md | 9 ++-- man/oci-image-tool-validate.1.md | 9 ++-- 9 files changed, 147 insertions(+), 104 deletions(-) diff --git a/cmd/oci-image-tool/create.go b/cmd/oci-image-tool/create.go index 3b5f518..6a2a7cc 100644 --- a/cmd/oci-image-tool/create.go +++ b/cmd/oci-image-tool/create.go @@ -31,7 +31,7 @@ var bundleTypes = []string{ type bundleCmd struct { typ string // the type to bundle, can be empty string - ref string + refs []string root string platform string } @@ -43,11 +43,15 @@ func createHandle(context *cli.Context) error { v := bundleCmd{ typ: context.String("type"), - ref: context.String("ref"), + refs: context.StringSlice("ref"), root: context.String("rootfs"), platform: context.String("platform"), } + if len(v.refs) == 0 { + return fmt.Errorf("ref must be provided") + } + if v.typ == "" { typ, err := image.Autodetect(context.Args()[0]) if err != nil { @@ -59,13 +63,13 @@ func createHandle(context *cli.Context) error { var err error switch v.typ { case image.TypeImageLayout: - err = image.CreateRuntimeBundleLayout(context.Args()[0], context.Args()[1], v.ref, v.root, v.platform) + err = image.CreateRuntimeBundleLayout(context.Args()[0], context.Args()[1], v.root, v.platform, v.refs) case image.TypeImageZip: - err = image.CreateRuntimeBundleZip(context.Args()[0], context.Args()[1], v.ref, v.root, v.platform) + err = image.CreateRuntimeBundleZip(context.Args()[0], context.Args()[1], v.root, v.platform, v.refs) case image.TypeImage: - err = image.CreateRuntimeBundleFile(context.Args()[0], context.Args()[1], v.ref, v.root, v.platform) + err = image.CreateRuntimeBundleFile(context.Args()[0], context.Args()[1], v.root, v.platform, v.refs) default: err = fmt.Errorf("cannot create %q", v.typ) @@ -87,10 +91,9 @@ var createCommand = cli.Command{ strings.Join(bundleTypes, ","), ), }, - cli.StringFlag{ + cli.StringSliceFlag{ Name: "ref", - Value: "v1.0", - Usage: "The ref pointing to the manifest of the OCI image. This must be present in the 'refs' subdirectory of the image.", + Usage: "A set of ref specify the search criteria for the validated reference, format is A=B. Only support 'name', 'platform.os' and 'digest' three cases.", }, cli.StringFlag{ Name: "rootfs", diff --git a/cmd/oci-image-tool/unpack.go b/cmd/oci-image-tool/unpack.go index 743575f..e46323c 100644 --- a/cmd/oci-image-tool/unpack.go +++ b/cmd/oci-image-tool/unpack.go @@ -31,7 +31,7 @@ var unpackTypes = []string{ type unpackCmd struct { typ string // the type to unpack, can be empty string - ref string + refs []string platform string } @@ -42,10 +42,14 @@ func unpackHandle(context *cli.Context) error { v := unpackCmd{ typ: context.String("type"), - ref: context.String("ref"), + refs: context.StringSlice("ref"), platform: context.String("platform"), } + if len(v.refs) == 0 { + return fmt.Errorf("ref must be provided") + } + if v.typ == "" { typ, err := image.Autodetect(context.Args()[0]) if err != nil { @@ -57,13 +61,13 @@ func unpackHandle(context *cli.Context) error { var err error switch v.typ { case image.TypeImageLayout: - err = image.UnpackLayout(context.Args()[0], context.Args()[1], v.ref, v.platform) + err = image.UnpackLayout(context.Args()[0], context.Args()[1], v.platform, v.refs) case image.TypeImageZip: - err = image.UnpackZip(context.Args()[0], context.Args()[1], v.ref, v.platform) + err = image.UnpackZip(context.Args()[0], context.Args()[1], v.platform, v.refs) case image.TypeImage: - err = image.UnpackFile(context.Args()[0], context.Args()[1], v.ref, v.platform) + err = image.UnpackFile(context.Args()[0], context.Args()[1], v.platform, v.refs) default: err = fmt.Errorf("cannot unpack %q", v.typ) @@ -84,10 +88,9 @@ var unpackCommand = cli.Command{ strings.Join(unpackTypes, ","), ), }, - cli.StringFlag{ + cli.StringSliceFlag{ Name: "ref", - Value: "v1.0", - Usage: "The ref pointing to the manifest of the OCI image. This must be present in the 'refs' subdirectory of the image.", + Usage: "A set of ref specify the search criteria for the validated reference, format is A=B. Only support 'name', 'platform.os' and 'digest' three cases.", }, cli.StringFlag{ Name: "platform", diff --git a/cmd/oci-image-tool/validate.go b/cmd/oci-image-tool/validate.go index 0782fa9..af7ed48 100644 --- a/cmd/oci-image-tool/validate.go +++ b/cmd/oci-image-tool/validate.go @@ -108,7 +108,7 @@ func validatePath(name string) error { } if len(v.refs) != 0 { - fmt.Printf("WARNING: type %q does not support refs, which are only appropriate if type is image or imageLayout.\n", typ) + fmt.Printf("WARNING: type %q does not support ref, which are only appropriate if type is image or imageLayout.\n", typ) } f, err := os.Open(name) @@ -143,7 +143,7 @@ var validateCommand = cli.Command{ }, cli.StringSliceFlag{ Name: "ref", - Usage: "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 or imageLayout.", + Usage: "A set of ref specify the search criteria for the validated reference. Format is A=B. Only support 'name', 'platform.os' and 'digest' three cases. Only applicable if type is image or imageLayout.", }, }, } diff --git a/image/descriptor.go b/image/descriptor.go index fb3ae97..a62f6fe 100644 --- a/image/descriptor.go +++ b/image/descriptor.go @@ -20,6 +20,7 @@ import ( "io" "os" "path/filepath" + "strings" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -27,8 +28,8 @@ import ( const indexPath = "index.json" -func listReferences(w walker) (map[string]*v1.Descriptor, error) { - refs := make(map[string]*v1.Descriptor) +func listReferences(w walker) ([]v1.Descriptor, error) { + var descs []v1.Descriptor var index v1.Index if err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { @@ -39,25 +40,21 @@ func listReferences(w walker) (map[string]*v1.Descriptor, error) { if err := json.NewDecoder(r).Decode(&index); err != nil { return err } - - for i := 0; i < len(index.Manifests); i++ { - if index.Manifests[i].Annotations[v1.AnnotationRefName] != "" { - refs[index.Manifests[i].Annotations[v1.AnnotationRefName]] = &index.Manifests[i] - } - } + descs = index.Manifests return nil }); err != nil { return nil, err } - return refs, nil + + return descs, nil } -func findDescriptor(w walker, name string) (*v1.Descriptor, error) { - var d v1.Descriptor +func findDescriptor(w walker, names []string) ([]v1.Descriptor, error) { + var descs []v1.Descriptor var index v1.Index - switch err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { + if err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { if info.IsDir() || filepath.Clean(path) != indexPath { return nil } @@ -66,22 +63,49 @@ func findDescriptor(w walker, name string) (*v1.Descriptor, error) { return err } - for i := 0; i < len(index.Manifests); i++ { - if index.Manifests[i].Annotations[v1.AnnotationRefName] == name { - d = index.Manifests[i] - return errEOW + descs = index.Manifests + for _, name := range names { + argsParts := strings.Split(name, "=") + if len(argsParts) != 2 { + return fmt.Errorf("each ref must contain two parts") + } + + switch argsParts[0] { + case "name": + for i := 0; i < len(descs); i++ { + if descs[i].Annotations[v1.AnnotationRefName] != argsParts[1] { + descs = append(descs[:i], descs[i+1:]...) + } + } + case "platform.os": + for i := 0; i < len(descs); i++ { + if descs[i].Platform != nil && index.Manifests[i].Platform.OS != argsParts[1] { + descs = append(descs[:i], descs[i+1:]...) + } + } + case "digest": + for i := 0; i < len(descs); i++ { + if string(descs[i].Digest) != argsParts[1] { + descs = append(descs[:i], descs[i+1:]...) + } + } + default: + return fmt.Errorf("criteria %q unimplemented", argsParts[0]) } } return nil - }); err { - case nil: - return nil, fmt.Errorf("index.json: descriptor %q not found", name) - case errEOW: - return &d, nil - default: + }); err != nil { return nil, err } + + if len(descs) == 0 { + return nil, fmt.Errorf("index.json: descriptor retrieved by refs %v is not match", names) + } else if len(descs) > 1 { + return nil, fmt.Errorf("index.json: descriptor retrieved by refs %v is not unique", names) + } + + return descs, nil } func validateDescriptor(d *v1.Descriptor, w walker, mts []string) error { diff --git a/image/image.go b/image/image.go index e5c0fe1..98e1317 100644 --- a/image/image.go +++ b/image/image.go @@ -65,36 +65,35 @@ var validRefMediaTypes = []string{ } func validate(w walker, refs []string, out *log.Logger) error { - if err := layoutValidate(w); err != nil { - return err - } + var descs []v1.Descriptor + var err error - ds, err := listReferences(w) - if err != nil { + if err = layoutValidate(w); err != nil { return err } - if len(refs) == 0 && len(ds) == 0 { - // TODO(runcom): ugly, we'll need a better way and library - // to express log levels. - // see https://github.com/opencontainers/image-spec/issues/288 - out.Print("WARNING: no descriptors found") - } if len(refs) == 0 { - for ref := range ds { - refs = append(refs, ref) + out.Print("No ref specified, verify all refs") + descs, err = listReferences(w) + if err != nil { + return err } - } - - for _, ref := range refs { - d, ok := ds[ref] - if !ok { - // TODO(runcom): - // soften this error to a warning if the user didn't ask for any specific reference - // with --ref but she's just validating the whole image. - return fmt.Errorf("reference %s not found", ref) + if len(descs) == 0 { + // TODO(runcom): ugly, we'll need a better way and library + // to express log levels. + // see https://github.com/opencontainers/image-spec/issues/288 + out.Print("WARNING: no descriptors found") + return nil + } + } else { + descs, err = findDescriptor(w, refs) + if err != nil { + return err } + } + for _, desc := range descs { + d := &desc if err = validateDescriptor(d, w, validRefMediaTypes); err != nil { return err } @@ -136,10 +135,10 @@ func validate(w walker, refs []string, out *log.Logger) error { } } } + } - if out != nil { - out.Printf("reference %q: OK", ref) - } + if out != nil && len(refs) > 0 { + out.Printf("reference %v: OK", refs) } return nil @@ -148,46 +147,47 @@ func validate(w walker, refs []string, out *log.Logger) error { // UnpackLayout walks through the file tree given by src and, using the layers // specified in the manifest pointed to by the given ref, unpacks all layers in // the given destination directory or returns an error if the unpacking failed. -func UnpackLayout(src, dest, ref, platform string) error { - return unpack(newPathWalker(src), dest, ref, platform) +func UnpackLayout(src, dest, platform string, refs []string) error { + return unpack(newPathWalker(src), dest, platform, refs) } // UnpackZip opens and walks through the zip file given by src and, using the layers // specified in the manifest pointed to by the given ref, unpacks all layers in // the given destination directory or returns an error if the unpacking failed. -func UnpackZip(src, dest, ref, platform string) error { - return unpack(newZipWalker(src), dest, ref, platform) +func UnpackZip(src, dest, platform string, refs []string) error { + return unpack(newZipWalker(src), dest, platform, refs) } // UnpackFile opens the file pointed by tarFileName and calls Unpack on it. -func UnpackFile(tarFileName, dest, ref, platform string) error { +func UnpackFile(tarFileName, dest, platform string, refs []string) error { f, err := os.Open(tarFileName) if err != nil { return errors.Wrap(err, "unable to open file") } defer f.Close() - return Unpack(f, dest, ref, platform) + return Unpack(f, dest, platform, refs) } // Unpack walks through the tar stream and, using the layers specified in // the manifest pointed to by the given ref, unpacks all layers in the given // destination directory or returns an error if the unpacking failed. // The destination will be created if it does not exist. -func Unpack(r io.ReadSeeker, dest, refName, platform string) error { - return unpack(newTarWalker(r), dest, refName, platform) +func Unpack(r io.ReadSeeker, dest, platform string, refs []string) error { + return unpack(newTarWalker(r), dest, platform, refs) } -func unpack(w walker, dest, refName, platform string) error { +func unpack(w walker, dest, platform string, refs []string) error { if err := layoutValidate(w); err != nil { return err } - ref, err := findDescriptor(w, refName) + descs, err := findDescriptor(w, refs) if err != nil { return err } + ref := &descs[0] if err = validateDescriptor(ref, w, validRefMediaTypes); err != nil { return err } @@ -231,46 +231,47 @@ func unpack(w walker, dest, refName, platform string) error { // 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, platform string) error { - return createRuntimeBundle(newPathWalker(src), dest, ref, root, platform) +func CreateRuntimeBundleLayout(src, dest, root, platform string, refs []string) error { + return createRuntimeBundle(newPathWalker(src), dest, root, platform, refs) } // CreateRuntimeBundleZip opens and walks through the zip file given by src // and creates an OCI runtime bundle in the given destination dest // or returns an error if the unpacking failed. -func CreateRuntimeBundleZip(src, dest, ref, root, platform string) error { - return createRuntimeBundle(newZipWalker(src), dest, ref, root, platform) +func CreateRuntimeBundleZip(src, dest, root, platform string, refs []string) error { + return createRuntimeBundle(newZipWalker(src), dest, root, platform, refs) } // CreateRuntimeBundleFile opens the file pointed by tarFile and calls // CreateRuntimeBundle. -func CreateRuntimeBundleFile(tarFile, dest, ref, root, platform string) error { +func CreateRuntimeBundleFile(tarFile, dest, root, platform string, refs []string) error { f, err := os.Open(tarFile) if err != nil { return errors.Wrap(err, "unable to open file") } defer f.Close() - return createRuntimeBundle(newTarWalker(f), dest, ref, root, platform) + return createRuntimeBundle(newTarWalker(f), dest, root, platform, refs) } // CreateRuntimeBundle walks through the given tar stream and // creates an OCI runtime bundle in the given destination dest // or returns an error if the unpacking failed. -func CreateRuntimeBundle(r io.ReadSeeker, dest, ref, root, platform string) error { - return createRuntimeBundle(newTarWalker(r), dest, ref, root, platform) +func CreateRuntimeBundle(r io.ReadSeeker, dest, root, platform string, refs []string) error { + return createRuntimeBundle(newTarWalker(r), dest, root, platform, refs) } -func createRuntimeBundle(w walker, dest, refName, rootfs, platform string) error { +func createRuntimeBundle(w walker, dest, rootfs, platform string, refs []string) error { if err := layoutValidate(w); err != nil { return err } - ref, err := findDescriptor(w, refName) + descs, err := findDescriptor(w, refs) if err != nil { return err } + ref := &descs[0] if err = validateDescriptor(ref, w, validRefMediaTypes); err != nil { return err } diff --git a/image/image_test.go b/image/image_test.go index ed73dce..7141d98 100644 --- a/image/image_test.go +++ b/image/image_test.go @@ -88,9 +88,14 @@ const ( ) var ( - refTag = []string{ - "latest", - "v1.0", + ref1 = []string{ + "name=latest", + "platform.os=linux", + } + + ref2 = []string{ + "name=v1.0", + "platform.os=linux", } indexJSON = `{ @@ -221,7 +226,7 @@ func TestImageLayout(t *testing.T) { il := imageLayout{ rootDir: root, layout: layoutStr, - ref: refTag, + ref: ref1, manifest: manifestStr, index: indexStr, indexjson: indexJSON, @@ -237,24 +242,24 @@ func TestImageLayout(t *testing.T) { t.Fatal(err) } - err = ValidateLayout(root, refTag, nil) + err = ValidateLayout(root, ref1, nil) if err != nil { t.Fatal(err) } - err = UnpackLayout(root, dest1, refTag[0], "") + err = UnpackLayout(root, dest1, "", ref1) if err != nil { t.Fatal(err) } - err = UnpackLayout(root, dest2, refTag[1], "linux:amd64") + err = UnpackLayout(root, dest2, "linux:amd64", ref2) if err != nil { t.Fatal(err) } - err = CreateRuntimeBundleLayout(root, dest3, refTag[0], "rootfs", "") + err = CreateRuntimeBundleLayout(root, dest3, "rootfs", "", ref1) if err != nil { t.Fatal(err) } - err = CreateRuntimeBundleLayout(root, dest4, refTag[1], "rootfs", "linux:amd64") + err = CreateRuntimeBundleLayout(root, dest4, "rootfs", "linux:amd64", ref2) if err != nil { t.Fatal(err) } diff --git a/man/oci-image-tool-create.1.md b/man/oci-image-tool-create.1.md index 290789a..fbd5ed7 100644 --- a/man/oci-image-tool-create.1.md +++ b/man/oci-image-tool-create.1.md @@ -17,8 +17,11 @@ runtime-spec-compatible `dest/config.json`. **--help** Print usage statement -**--ref**="" - The ref pointing to the manifest of the OCI image. This must be present in the "refs" subdirectory of the image. (default "v1.0") +**--ref**=[] + Specify the search criteria for the validated reference, format is A=B. + Reference should point to a manifest or index. + e.g. --ref name=v1.0 --ref platform.os=latest + Only support `name`, `platform.os` and `digest` three cases. **--rootfs**="" 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") @@ -35,7 +38,7 @@ runtime-spec-compatible `dest/config.json`. ``` $ skopeo copy docker://busybox oci:busybox-oci:latest $ mkdir busybox-bundle -$ oci-image-tool create --ref latest busybox-oci busybox-bundle +$ oci-image-tool create --ref name=latest busybox-oci busybox-bundle $ cd busybox-bundle && sudo runc run busybox [...] ``` diff --git a/man/oci-image-tool-unpack.1.md b/man/oci-image-tool-unpack.1.md index fe1de95..d43f574 100644 --- a/man/oci-image-tool-unpack.1.md +++ b/man/oci-image-tool-unpack.1.md @@ -14,8 +14,11 @@ oci-image-tool unpack \- Unpack an image or image source layout **--help** Print usage statement -**--ref**="" - The ref pointing to the manifest to be unpacked. This must be present in the "refs" subdirectory of the image. (default "v1.0") +**--ref**=[] + Specify the search criteria for the validated reference, format is A=B. + Reference should point to a manifest or index. + e.g. --ref name=v1.0 --ref platform.os=latest + Only support `name`, `platform.os` and `digest` three cases. **--type**="" Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image,imageZip" @@ -29,7 +32,7 @@ oci-image-tool unpack \- Unpack an image or image source layout ``` $ skopeo copy docker://busybox oci:busybox-oci:latest $ mkdir busybox-bundle -$ oci-image-tool unpack --ref latest busybox-oci busybox-bundle +$ oci-image-tool unpack --ref name=latest busybox-oci busybox-bundle $ tree busybox-bundle busybox-bundle ├── bin diff --git a/man/oci-image-tool-validate.1.md b/man/oci-image-tool-validate.1.md index 5e05101..cf2b4c5 100644 --- a/man/oci-image-tool-validate.1.md +++ b/man/oci-image-tool-validate.1.md @@ -16,9 +16,10 @@ oci-image-tool validate \- Validate one or more image files Print usage statement **--ref**=[] - The reference to validate (should point to a manifest). - Can be specified multiple times to validate multiple references. - `NAME` must be present in the `refs` subdirectory of the image. + Specify the search criteria for the validated reference, format is A=B. + Reference should point to a manifest or index. + e.g. --ref name=v1.0 --ref platform.os=latest + Only support `name`, `platform.os` and `digest` three cases. Only applicable if type is image or imageLayout. **--type**="" @@ -27,7 +28,7 @@ oci-image-tool validate \- Validate one or more image files # EXAMPLES ``` $ skopeo copy docker://busybox oci:busybox-oci:latest -$ oci-image-tool validate --type imageLayout --ref latest busybox-oci +$ oci-image-tool validate --type imageLayout --ref name=latest busybox-oci busybox-oci: OK ```