From ca603fcbfce868c88a466b69b1388511ac643b3e Mon Sep 17 00:00:00 2001 From: zhouhao Date: Tue, 25 Oct 2016 16:37:40 +0800 Subject: [PATCH 1/3] Add index validation Signed-off-by: zhouhao --- image/descriptor.go | 6 +- image/image.go | 136 ++++++++++++++++++++++++++++++++++++++------ image/index.go | 70 +++++++++++++++++++++++ 3 files changed, 194 insertions(+), 18 deletions(-) create mode 100644 image/index.go diff --git a/image/descriptor.go b/image/descriptor.go index dd04d2c..290bcf9 100644 --- a/image/descriptor.go +++ b/image/descriptor.go @@ -25,12 +25,14 @@ import ( "github.com/pkg/errors" ) +const indexPath = "index.json" + func listReferences(w walker) (map[string]*v1.Descriptor, error) { refs := make(map[string]*v1.Descriptor) var index v1.Index if err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { - if info.IsDir() || filepath.Clean(path) != "index.json" { + if info.IsDir() || filepath.Clean(path) != indexPath { return nil } @@ -56,7 +58,7 @@ func findDescriptor(w walker, name string) (*v1.Descriptor, error) { var index v1.Index switch err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { - if info.IsDir() || filepath.Clean(path) != "index.json" { + if info.IsDir() || filepath.Clean(path) != indexPath { return nil } diff --git a/image/image.go b/image/image.go index a92ea23..03a940e 100644 --- a/image/image.go +++ b/image/image.go @@ -92,14 +92,44 @@ func validate(w walker, refs []string, out *log.Logger) error { return err } - m, err := findManifest(w, d) - if err != nil { - return err + if d.MediaType == validRefMediaTypes[0] { + m, err := findManifest(w, d) + if err != nil { + return err + } + + if err := m.validate(w); err != nil { + return err + } } - if err := m.validate(w); err != nil { - return err + if d.MediaType == validRefMediaTypes[1] { + index, err := findIndex(w, d) + if err != nil { + return err + } + + if err := validateIndex(index, w); err != nil { + return err + } + + if len(index.Manifests) == 0 { + fmt.Println("warning: no manifests found") + return nil + } + + for _, manifest := range index.Manifests { + m, err := findManifest(w, &(manifest.Descriptor)) + if err != nil { + return err + } + + if err := m.validate(w); err != nil { + return err + } + } } + if out != nil { out.Printf("reference %q: OK", ref) } @@ -147,16 +177,51 @@ func unpack(w walker, dest, refName string) error { return err } - m, err := findManifest(w, ref) - if err != nil { - return err + if ref.MediaType == validRefMediaTypes[0] { + m, err := findManifest(w, ref) + if err != nil { + return err + } + + if err := m.validate(w); err != nil { + return err + } + + return m.unpack(w, dest) } - if err = m.validate(w); err != nil { - return err + if ref.MediaType == validRefMediaTypes[1] { + index, err := findIndex(w, ref) + if err != nil { + return err + } + + if err := validateIndex(index, w); err != nil { + return err + } + + if len(index.Manifests) == 0 { + fmt.Println("warning: no manifests found") + return nil + } + + for _, manifest := range index.Manifests { + m, err := findManifest(w, &(manifest.Descriptor)) + if err != nil { + return err + } + + if err := m.validate(w); err != nil { + return err + } + + if err := m.unpack(w, dest); err != nil { + return err + } + } } - return m.unpack(w, dest) + return nil } // CreateRuntimeBundleLayout walks through the file tree given by src and @@ -199,15 +264,54 @@ func createRuntimeBundle(w walker, dest, refName, rootfs string) error { return err } - m, err := findManifest(w, ref) - if err != nil { - return err + if ref.MediaType == validRefMediaTypes[0] { + m, err := findManifest(w, ref) + if err != nil { + return err + } + + if err := m.validate(w); err != nil { + return err + } + + return createRuntimebundle(w, m, dest, rootfs) } - if err = m.validate(w); err != nil { - return err + if ref.MediaType == validRefMediaTypes[1] { + index, err := findIndex(w, ref) + if err != nil { + return err + } + + if err := validateIndex(index, w); err != nil { + return err + } + + if len(index.Manifests) == 0 { + fmt.Println("warning: no manifests found") + return nil + } + + for _, manifest := range index.Manifests { + m, err := findManifest(w, &(manifest.Descriptor)) + if err != nil { + return err + } + + if err := m.validate(w); err != nil { + return err + } + + if err := createRuntimebundle(w, m, dest, rootfs); err != nil { + return err + } + } } + return nil +} + +func createRuntimebundle(w walker, m *manifest, dest, rootfs string) error { c, err := findConfig(w, &m.Config) if err != nil { return err diff --git a/image/index.go b/image/index.go new file mode 100644 index 0000000..ddbe30f --- /dev/null +++ b/image/index.go @@ -0,0 +1,70 @@ +// 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" + "path/filepath" + + "github.com/opencontainers/image-spec/schema" + "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +func findIndex(w walker, d *v1.Descriptor) (*v1.ImageIndex, error) { + var index v1.ImageIndex + + switch err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { + if info.IsDir() || filepath.Clean(path) != indexPath { + return nil + } + + buf, err := ioutil.ReadAll(r) + if err != nil { + return errors.Wrapf(err, "%s: error reading index", path) + } + + if err := schema.ValidatorMediaTypeImageIndex.Validate(bytes.NewReader(buf)); err != nil { + return errors.Wrapf(err, "%s: index validation failed", path) + } + + if err := json.Unmarshal(buf, &index); err != nil { + return err + } + + return errEOW + }); err { + case errEOW: + return &index, nil + case nil: + return nil, fmt.Errorf("index.json not found") + default: + return nil, err + } +} + +func validateIndex(index *v1.ImageIndex, w walker) error { + for _, manifest := range index.Manifests { + if err := validateDescriptor(&manifest.Descriptor, w, []string{v1.MediaTypeImageManifest}); err != nil { + return errors.Wrap(err, "manifest validation failed") + } + } + return nil +} From 4829194490c2d230ac962111f04ec85e22eedd70 Mon Sep 17 00:00:00 2001 From: zhouhao Date: Wed, 28 Jun 2017 16:11:56 +0800 Subject: [PATCH 2/3] Increase platform option Signed-off-by: zhouhao --- cmd/oci-image-tool/create.go | 22 +++--- cmd/oci-image-tool/unpack.go | 18 +++-- completions/bash/oci-image-tool | 4 +- image/image.go | 114 ++++++++++++++++++-------------- image/index.go | 8 +-- man/oci-image-tool-create.1.md | 4 ++ man/oci-image-tool-unpack.1.md | 5 ++ 7 files changed, 104 insertions(+), 71 deletions(-) diff --git a/cmd/oci-image-tool/create.go b/cmd/oci-image-tool/create.go index d1a9e48..6812c64 100644 --- a/cmd/oci-image-tool/create.go +++ b/cmd/oci-image-tool/create.go @@ -29,9 +29,10 @@ var bundleTypes = []string{ } type bundleCmd struct { - typ string // the type to bundle, can be empty string - ref string - root string + typ string // the type to bundle, can be empty string + ref string + root string + platform []string } func createHandle(context *cli.Context) error { @@ -40,9 +41,10 @@ func createHandle(context *cli.Context) error { } v := bundleCmd{ - typ: context.String("type"), - ref: context.String("ref"), - root: context.String("rootfs"), + typ: context.String("type"), + ref: context.String("ref"), + root: context.String("rootfs"), + platform: context.StringSlice("platform"), } if v.typ == "" { @@ -56,10 +58,10 @@ 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) + err = image.CreateRuntimeBundleLayout(context.Args()[0], context.Args()[1], v.ref, v.root, v.platform) case image.TypeImage: - err = image.CreateRuntimeBundleFile(context.Args()[0], context.Args()[1], v.ref, v.root) + err = image.CreateRuntimeBundleFile(context.Args()[0], context.Args()[1], v.ref, v.root, v.platform) default: err = fmt.Errorf("cannot create %q", v.typ) @@ -95,5 +97,9 @@ var createCommand = cli.Command{ Value: "rootfs", Usage: "A directory representing the root filesystem of the container in the OCI runtime bundle. It is strongly recommended to keep the default value.", }, + cli.StringSliceFlag{ + Name: "platform", + Usage: "The platform contains os and arch. Filter manifests according to the conditions provided. Only applicable if reftype is index.", + }, }, } diff --git a/cmd/oci-image-tool/unpack.go b/cmd/oci-image-tool/unpack.go index 7144dd2..a6123e0 100644 --- a/cmd/oci-image-tool/unpack.go +++ b/cmd/oci-image-tool/unpack.go @@ -29,8 +29,9 @@ var unpackTypes = []string{ } type unpackCmd struct { - typ string // the type to unpack, can be empty string - ref string + typ string // the type to unpack, can be empty string + ref string + platform []string } func unpackHandle(context *cli.Context) error { @@ -39,8 +40,9 @@ func unpackHandle(context *cli.Context) error { } v := unpackCmd{ - typ: context.String("type"), - ref: context.String("ref"), + typ: context.String("type"), + ref: context.String("ref"), + platform: context.StringSlice("platform"), } if v.typ == "" { @@ -54,10 +56,10 @@ 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) + err = image.UnpackLayout(context.Args()[0], context.Args()[1], v.ref, v.platform) case image.TypeImage: - err = image.UnpackFile(context.Args()[0], context.Args()[1], v.ref) + err = image.UnpackFile(context.Args()[0], context.Args()[1], v.ref, v.platform) default: err = fmt.Errorf("cannot unpack %q", v.typ) @@ -86,5 +88,9 @@ var unpackCommand = cli.Command{ 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.", }, + cli.StringSliceFlag{ + Name: "platform", + Usage: "The platform contains os and arch conditions. Filter manifests according to the conditions provided. Only applicable if reftype is index.", + }, }, } diff --git a/completions/bash/oci-image-tool b/completions/bash/oci-image-tool index d796aa2..847dfed 100644 --- a/completions/bash/oci-image-tool +++ b/completions/bash/oci-image-tool @@ -150,7 +150,7 @@ _oci-image-tool_create() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--type --ref --rootfs --help -h" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--type --ref --rootfs --platform --help -h" -- "$cur" ) ) ;; esac @@ -166,7 +166,7 @@ _oci-image-tool_unpack() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--type --ref --help -h" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--type --ref --platform --help -h" -- "$cur" ) ) ;; esac diff --git a/image/image.go b/image/image.go index 03a940e..4e2c12d 100644 --- a/image/image.go +++ b/image/image.go @@ -119,7 +119,7 @@ func validate(w walker, refs []string, out *log.Logger) error { } for _, manifest := range index.Manifests { - m, err := findManifest(w, &(manifest.Descriptor)) + m, err := findManifest(w, &manifest) if err != nil { return err } @@ -140,30 +140,30 @@ 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 string) error { - return unpack(newPathWalker(src), dest, ref) +func UnpackLayout(src, dest, ref string, platform []string) error { + return unpack(newPathWalker(src), dest, ref, platform) } // UnpackFile opens the file pointed by tarFileName and calls Unpack on it. -func UnpackFile(tarFileName, dest, ref string) error { +func UnpackFile(tarFileName, dest, ref string, platform []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) + return Unpack(f, dest, ref, platform) } // 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 string) error { - return unpack(newTarWalker(r), dest, refName) +func Unpack(r io.ReadSeeker, dest, refName string, platform []string) error { + return unpack(newTarWalker(r), dest, refName, platform) } -func unpack(w walker, dest, refName string) error { +func unpack(w walker, dest, refName string, platform []string) error { if err := layoutValidate(w); err != nil { return err } @@ -196,28 +196,17 @@ func unpack(w walker, dest, refName string) error { return err } - if err := validateIndex(index, w); err != nil { + if err = validateIndex(index, w); err != nil { return err } - if len(index.Manifests) == 0 { - fmt.Println("warning: no manifests found") - return nil + manifests, err := filterManifest(w, index.Manifests, platform) + if err != nil { + return err } - for _, manifest := range index.Manifests { - m, err := findManifest(w, &(manifest.Descriptor)) - if err != nil { - return err - } - - if err := m.validate(w); err != nil { - return err - } - - if err := m.unpack(w, dest); err != nil { - return err - } + for _, m := range manifests { + return m.unpack(w, dest) } } @@ -227,30 +216,30 @@ func unpack(w walker, dest, refName 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 string) error { - return createRuntimeBundle(newPathWalker(src), dest, ref, root) +func CreateRuntimeBundleLayout(src, dest, ref, root string, platform []string) error { + return createRuntimeBundle(newPathWalker(src), dest, ref, root, platform) } // CreateRuntimeBundleFile opens the file pointed by tarFile and calls // CreateRuntimeBundle. -func CreateRuntimeBundleFile(tarFile, dest, ref, root string) error { +func CreateRuntimeBundleFile(tarFile, dest, ref, root string, platform []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) + return createRuntimeBundle(newTarWalker(f), dest, ref, root, platform) } // 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 string) error { - return createRuntimeBundle(newTarWalker(r), dest, ref, root) +func CreateRuntimeBundle(r io.ReadSeeker, dest, ref, root string, platform []string) error { + return createRuntimeBundle(newTarWalker(r), dest, ref, root, platform) } -func createRuntimeBundle(w walker, dest, refName, rootfs string) error { +func createRuntimeBundle(w walker, dest, refName, rootfs string, platform []string) error { if err := layoutValidate(w); err != nil { return err } @@ -274,7 +263,7 @@ func createRuntimeBundle(w walker, dest, refName, rootfs string) error { return err } - return createRuntimebundle(w, m, dest, rootfs) + return createBundle(w, m, dest, rootfs) } if ref.MediaType == validRefMediaTypes[1] { @@ -283,35 +272,24 @@ func createRuntimeBundle(w walker, dest, refName, rootfs string) error { return err } - if err := validateIndex(index, w); err != nil { + if err = validateIndex(index, w); err != nil { return err } - if len(index.Manifests) == 0 { - fmt.Println("warning: no manifests found") - return nil + manifests, err := filterManifest(w, index.Manifests, platform) + if err != nil { + return err } - for _, manifest := range index.Manifests { - m, err := findManifest(w, &(manifest.Descriptor)) - if err != nil { - return err - } - - if err := m.validate(w); err != nil { - return err - } - - if err := createRuntimebundle(w, m, dest, rootfs); err != nil { - return err - } + for _, m := range manifests { + return createBundle(w, m, dest, rootfs) } } return nil } -func createRuntimebundle(w walker, m *manifest, dest, rootfs string) error { +func createBundle(w walker, m *manifest, dest, rootfs string) error { c, err := findConfig(w, &m.Config) if err != nil { return err @@ -344,3 +322,37 @@ func createRuntimebundle(w walker, m *manifest, dest, rootfs string) error { return json.NewEncoder(f).Encode(spec) } + +// filertManifest returns a filtered list of manifests +func filterManifest(w walker, Manifests []v1.Descriptor, platform []string) ([]*manifest, error) { + var manifests []*manifest + + if len(Manifests) == 0 { + fmt.Println("warning: no manifests found") + return manifests, nil + } + + if len(platform) != 2 { + return manifests, fmt.Errorf("platform must have os and arch") + } + + for _, manifest := range Manifests { + m, err := findManifest(w, &manifest) + if err != nil { + return manifests, err + } + + if err := m.validate(w); err != nil { + return manifests, err + } + if manifest.Platform.OS == platform[0] && manifest.Platform.Architecture == platform[1] { + manifests = append(manifests, m) + } + } + + if len(manifests) == 0 { + return manifests, fmt.Errorf("There is no matching manifest") + } + + return manifests, nil +} diff --git a/image/index.go b/image/index.go index ddbe30f..d41f8f0 100644 --- a/image/index.go +++ b/image/index.go @@ -28,8 +28,8 @@ import ( "github.com/pkg/errors" ) -func findIndex(w walker, d *v1.Descriptor) (*v1.ImageIndex, error) { - var index v1.ImageIndex +func findIndex(w walker, d *v1.Descriptor) (*v1.Index, error) { + var index v1.Index switch err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { if info.IsDir() || filepath.Clean(path) != indexPath { @@ -60,9 +60,9 @@ func findIndex(w walker, d *v1.Descriptor) (*v1.ImageIndex, error) { } } -func validateIndex(index *v1.ImageIndex, w walker) error { +func validateIndex(index *v1.Index, w walker) error { for _, manifest := range index.Manifests { - if err := validateDescriptor(&manifest.Descriptor, w, []string{v1.MediaTypeImageManifest}); err != nil { + if err := validateDescriptor(&manifest, w, []string{v1.MediaTypeImageManifest}); err != nil { return errors.Wrap(err, "manifest validation failed") } } diff --git a/man/oci-image-tool-create.1.md b/man/oci-image-tool-create.1.md index 869a41f..5a1d08e 100644 --- a/man/oci-image-tool-create.1.md +++ b/man/oci-image-tool-create.1.md @@ -26,6 +26,10 @@ runtime-spec-compatible `dest/config.json`. **--type**="" Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image" +**--platform**="" + The platform contains os and arch. Filter manifests according to the conditions provided. + Only applicable if reftype is index. + # EXAMPLES ``` $ skopeo copy docker://busybox oci:busybox-oci diff --git a/man/oci-image-tool-unpack.1.md b/man/oci-image-tool-unpack.1.md index 9b2bccb..d632758 100644 --- a/man/oci-image-tool-unpack.1.md +++ b/man/oci-image-tool-unpack.1.md @@ -20,6 +20,11 @@ oci-image-tool unpack \- Unpack an image or image source layout **--type**="" Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image" +**--platform**="" + The platform contains os and arch. Filter manifests according to the conditions provided. + Only applicable if reftype is index. + + # EXAMPLES ``` $ skopeo copy docker://busybox oci:busybox-oci From 3c7c0e7658ceb20a33ee186900ed67e0e24a11b0 Mon Sep 17 00:00:00 2001 From: zhouhao Date: Mon, 17 Jul 2017 14:40:14 +0800 Subject: [PATCH 3/3] add test to index Signed-off-by: zhouhao --- cmd/oci-image-tool/create.go | 8 +-- cmd/oci-image-tool/unpack.go | 8 +-- image/image.go | 30 ++++---- image/image_test.go | 126 +++++++++++++++++++++++---------- image/index.go | 5 +- man/oci-image-tool-create.1.md | 3 +- man/oci-image-tool-unpack.1.md | 4 +- 7 files changed, 121 insertions(+), 63 deletions(-) diff --git a/cmd/oci-image-tool/create.go b/cmd/oci-image-tool/create.go index 6812c64..be02286 100644 --- a/cmd/oci-image-tool/create.go +++ b/cmd/oci-image-tool/create.go @@ -32,7 +32,7 @@ type bundleCmd struct { typ string // the type to bundle, can be empty string ref string root string - platform []string + platform string } func createHandle(context *cli.Context) error { @@ -44,7 +44,7 @@ func createHandle(context *cli.Context) error { typ: context.String("type"), ref: context.String("ref"), root: context.String("rootfs"), - platform: context.StringSlice("platform"), + platform: context.String("platform"), } if v.typ == "" { @@ -97,9 +97,9 @@ var createCommand = cli.Command{ Value: "rootfs", Usage: "A directory representing the root filesystem of the container in the OCI runtime bundle. It is strongly recommended to keep the default value.", }, - cli.StringSliceFlag{ + cli.StringFlag{ Name: "platform", - Usage: "The platform contains os and arch. Filter manifests according to the conditions provided. Only applicable if reftype is index.", + Usage: "Specify the os and architecture of the manifest, format is OS:Architecture. Only applicable if reftype is index.", }, }, } diff --git a/cmd/oci-image-tool/unpack.go b/cmd/oci-image-tool/unpack.go index a6123e0..ab5c817 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 - platform []string + platform string } func unpackHandle(context *cli.Context) error { @@ -42,7 +42,7 @@ func unpackHandle(context *cli.Context) error { v := unpackCmd{ typ: context.String("type"), ref: context.String("ref"), - platform: context.StringSlice("platform"), + platform: context.String("platform"), } if v.typ == "" { @@ -88,9 +88,9 @@ var unpackCommand = cli.Command{ 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.", }, - cli.StringSliceFlag{ + cli.StringFlag{ Name: "platform", - Usage: "The platform contains os and arch conditions. Filter manifests according to the conditions provided. Only applicable if reftype is index.", + Usage: "Specify the os and architecture of the manifest, format is OS:Architecture. Only applicable if reftype is index.", }, }, } diff --git a/image/image.go b/image/image.go index 4e2c12d..e7ddd54 100644 --- a/image/image.go +++ b/image/image.go @@ -21,6 +21,7 @@ import ( "log" "os" "path/filepath" + "strings" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -140,12 +141,12 @@ 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 string, platform []string) error { +func UnpackLayout(src, dest, ref string, platform string) error { return unpack(newPathWalker(src), dest, ref, platform) } // UnpackFile opens the file pointed by tarFileName and calls Unpack on it. -func UnpackFile(tarFileName, dest, ref string, platform []string) error { +func UnpackFile(tarFileName, dest, ref string, platform string) error { f, err := os.Open(tarFileName) if err != nil { return errors.Wrap(err, "unable to open file") @@ -159,11 +160,11 @@ func UnpackFile(tarFileName, dest, ref string, platform []string) error { // 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 string, platform []string) error { +func Unpack(r io.ReadSeeker, dest, refName string, platform string) error { return unpack(newTarWalker(r), dest, refName, platform) } -func unpack(w walker, dest, refName string, platform []string) error { +func unpack(w walker, dest, refName string, platform string) error { if err := layoutValidate(w); err != nil { return err } @@ -216,13 +217,13 @@ func unpack(w walker, dest, refName string, 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 string, platform []string) error { +func CreateRuntimeBundleLayout(src, dest, ref, root string, platform string) error { return createRuntimeBundle(newPathWalker(src), dest, ref, root, platform) } // CreateRuntimeBundleFile opens the file pointed by tarFile and calls // CreateRuntimeBundle. -func CreateRuntimeBundleFile(tarFile, dest, ref, root string, platform []string) error { +func CreateRuntimeBundleFile(tarFile, dest, ref, root string, platform string) error { f, err := os.Open(tarFile) if err != nil { return errors.Wrap(err, "unable to open file") @@ -235,11 +236,11 @@ func CreateRuntimeBundleFile(tarFile, dest, ref, root string, platform []string) // 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 string, platform []string) error { +func CreateRuntimeBundle(r io.ReadSeeker, dest, ref, root string, platform string) error { return createRuntimeBundle(newTarWalker(r), dest, ref, root, platform) } -func createRuntimeBundle(w walker, dest, refName, rootfs string, platform []string) error { +func createRuntimeBundle(w walker, dest, refName, rootfs string, platform string) error { if err := layoutValidate(w); err != nil { return err } @@ -324,18 +325,19 @@ func createBundle(w walker, m *manifest, dest, rootfs string) error { } // filertManifest returns a filtered list of manifests -func filterManifest(w walker, Manifests []v1.Descriptor, platform []string) ([]*manifest, error) { +func filterManifest(w walker, Manifests []v1.Descriptor, platform string) ([]*manifest, error) { var manifests []*manifest + argsParts := strings.Split(platform, ":") + if len(argsParts) != 2 { + return manifests, fmt.Errorf("platform must have os and arch when reftype is index") + } + if len(Manifests) == 0 { fmt.Println("warning: no manifests found") return manifests, nil } - if len(platform) != 2 { - return manifests, fmt.Errorf("platform must have os and arch") - } - for _, manifest := range Manifests { m, err := findManifest(w, &manifest) if err != nil { @@ -345,7 +347,7 @@ func filterManifest(w walker, Manifests []v1.Descriptor, platform []string) ([]* if err := m.validate(w); err != nil { return manifests, err } - if manifest.Platform.OS == platform[0] && manifest.Platform.Architecture == platform[1] { + if strings.EqualFold(manifest.Platform.OS, argsParts[0]) && strings.EqualFold(manifest.Platform.Architecture, argsParts[1]) { manifests = append(manifests, m) } } diff --git a/image/image_test.go b/image/image_test.go index 61ec39e..f8d12a1 100644 --- a/image/image_test.go +++ b/image/image_test.go @@ -31,7 +31,6 @@ import ( ) const ( - refTag = "latest" layoutStr = `{"imageLayoutVersion": "1.0.0"}` configStr = `{ @@ -40,7 +39,6 @@ const ( "architecture": "amd64", "os": "linux", "config": { - "User": "alice", "ExposedPorts": { "8080/tcp": {} }, @@ -90,17 +88,43 @@ const ( ) var ( + refTag = []string{ + "latest", + "v1.0", + } + + indexJSON = `{ + "schemaVersion": 2, + "manifests": [ + { + "mediaType": "application/vnd.oci.image.index.v1+json", + "size": , + "digest": "", + "annotations": { + "org.opencontainers.ref.name": "v1.0" + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": , + "digest": "", + "platform": { + "architecture": "ppc64le", + "os": "linux" + }, + "annotations": { + "org.opencontainers.ref.name": "latest" + } + } + ], + "annotations": { + "com.example.index.revision": "r124356" + } +} + ` indexStr = `{ "schemaVersion": 2, "manifests": [ - { - "mediaType": "application/vnd.oci.image.index.v1+json", - "size": , - "digest": "", - "annotations": { - "org.opencontainers.ref.name": "v1.0" - } - }, { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": , @@ -108,18 +132,15 @@ var ( "platform": { "architecture": "ppc64le", "os": "linux" - }, - "annotations": { - "org.opencontainers.ref.name": "latest" } }, { - "mediaType": "application/xml", + "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": , "digest": "", - "annotations": { - "org.freedesktop.specifications.metainfo.version": "1.0", - "org.freedesktop.specifications.metainfo.type": "AppStream" + "platform": { + "architecture": "amd64", + "os": "linux" } } ], @@ -129,10 +150,10 @@ var ( } ` manifestStr = `{ - "annotations": { + "annotations": { "org.freedesktop.specifications.metainfo.version": "1.0", "org.freedesktop.specifications.metainfo.type": "AppStream" - }, + }, "config": { "digest": "", "mediaType": "application/vnd.oci.image.config.v1+json", @@ -156,12 +177,14 @@ type tarContent struct { } type imageLayout struct { - rootDir string - layout string - ref string - manifest string - config string - tarList []tarContent + rootDir string + layout string + ref []string + manifest string + index string + config string + indexjson string + tarList []tarContent } func TestValidateLayout(t *testing.T) { @@ -172,11 +195,13 @@ func TestValidateLayout(t *testing.T) { defer os.RemoveAll(root) il := imageLayout{ - rootDir: root, - layout: layoutStr, - ref: refTag, - manifest: manifestStr, - config: configStr, + rootDir: root, + layout: layoutStr, + ref: refTag, + manifest: manifestStr, + index: indexStr, + indexjson: indexJSON, + config: configStr, tarList: []tarContent{ {&tar.Header{Name: "test", Size: 4, Mode: 0600}, []byte("test")}, }, @@ -188,7 +213,7 @@ func TestValidateLayout(t *testing.T) { t.Fatal(err) } - err = ValidateLayout(root, []string{refTag}, nil) + err = ValidateLayout(root, refTag, nil) if err != nil { t.Fatal(err) } @@ -226,8 +251,22 @@ func createImageLayoutBundle(il imageLayout) error { if err != nil { return err } + il.index = strings.Replace(il.index, "", string(desc.Digest), -1) + il.index = strings.Replace(il.index, "", strconv.FormatInt(desc.Size, 10), -1) + + il.indexjson = strings.Replace(il.indexjson, "", string(desc.Digest), -1) + il.indexjson = strings.Replace(il.indexjson, "", strconv.FormatInt(desc.Size, 10), -1) + + // create index blob file + desc, err = createIndexFile(il.rootDir, il.index) + if err != nil { + return err + } + il.indexjson = strings.Replace(il.indexjson, "", string(desc.Digest), -1) + il.indexjson = strings.Replace(il.indexjson, "", strconv.FormatInt(desc.Size, 10), -1) - return createIndexFile(il.rootDir, desc) + // create index.json file + return createIndexJSON(il.rootDir, il.indexjson) } func createLayoutFile(root string) error { @@ -241,19 +280,34 @@ func createLayoutFile(root string) error { return err } -func createIndexFile(root string, mft v1.Descriptor) error { +func createIndexJSON(root string, str string) error { indexpath := filepath.Join(root, "index.json") f, err := os.Create(indexpath) if err != nil { return err } defer f.Close() - indexStr = strings.Replace(indexStr, "", string(mft.Digest), -1) - indexStr = strings.Replace(indexStr, "", strconv.FormatInt(mft.Size, 10), -1) - _, err = io.Copy(f, bytes.NewBuffer([]byte(indexStr))) + _, err = io.Copy(f, bytes.NewBuffer([]byte(str))) + return err } +func createIndexFile(root, str string) (v1.Descriptor, error) { + name := filepath.Join(root, "blobs", "sha256", "test-index") + f, err := os.Create(name) + if err != nil { + return v1.Descriptor{}, err + } + defer f.Close() + + _, err = io.Copy(f, bytes.NewBuffer([]byte(str))) + if err != nil { + return v1.Descriptor{}, err + } + + return createHashedBlob(name) +} + func createManifestFile(root, str string) (v1.Descriptor, error) { name := filepath.Join(root, "blobs", "sha256", "test-manifest") f, err := os.Create(name) diff --git a/image/index.go b/image/index.go index d41f8f0..f0c8d61 100644 --- a/image/index.go +++ b/image/index.go @@ -30,9 +30,10 @@ import ( func findIndex(w walker, d *v1.Descriptor) (*v1.Index, error) { var index v1.Index + ipath := filepath.Join("blobs", string(d.Digest.Algorithm()), d.Digest.Hex()) switch err := w.walk(func(path string, info os.FileInfo, r io.Reader) error { - if info.IsDir() || filepath.Clean(path) != indexPath { + if info.IsDir() || filepath.Clean(path) != ipath { return nil } @@ -54,7 +55,7 @@ func findIndex(w walker, d *v1.Descriptor) (*v1.Index, error) { case errEOW: return &index, nil case nil: - return nil, fmt.Errorf("index.json not found") + return nil, fmt.Errorf("index not found") default: return nil, err } diff --git a/man/oci-image-tool-create.1.md b/man/oci-image-tool-create.1.md index 5a1d08e..2e9ce6e 100644 --- a/man/oci-image-tool-create.1.md +++ b/man/oci-image-tool-create.1.md @@ -27,7 +27,8 @@ runtime-spec-compatible `dest/config.json`. Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image" **--platform**="" - The platform contains os and arch. Filter manifests according to the conditions provided. + Specify the os and architecture of the manifest, format is OS:Architecture. + e.g. --platform linux:amd64 Only applicable if reftype is index. # EXAMPLES diff --git a/man/oci-image-tool-unpack.1.md b/man/oci-image-tool-unpack.1.md index d632758..95d3aec 100644 --- a/man/oci-image-tool-unpack.1.md +++ b/man/oci-image-tool-unpack.1.md @@ -21,10 +21,10 @@ oci-image-tool unpack \- Unpack an image or image source layout Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image" **--platform**="" - The platform contains os and arch. Filter manifests according to the conditions provided. + Specify the os and architecture of the manifest, format is OS:Architecture. + e.g. --platform linux:amd64 Only applicable if reftype is index. - # EXAMPLES ``` $ skopeo copy docker://busybox oci:busybox-oci