Skip to content
This repository was archived by the owner on Jan 15, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/oci-image-tool/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
var bundleTypes = []string{
image.TypeImageLayout,
image.TypeImage,
image.TypeImageZip,
}

type bundleCmd struct {
Expand Down Expand Up @@ -60,6 +61,9 @@ func createHandle(context *cli.Context) error {
case image.TypeImageLayout:
err = image.CreateRuntimeBundleLayout(context.Args()[0], context.Args()[1], v.ref, v.root, v.platform)

case image.TypeImageZip:
err = image.CreateRuntimeBundleZip(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, v.platform)

Expand Down
4 changes: 4 additions & 0 deletions cmd/oci-image-tool/unpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
var unpackTypes = []string{
image.TypeImageLayout,
image.TypeImage,
image.TypeImageZip,
}

type unpackCmd struct {
Expand Down Expand Up @@ -58,6 +59,9 @@ func unpackHandle(context *cli.Context) error {
case image.TypeImageLayout:
err = image.UnpackLayout(context.Args()[0], context.Args()[1], v.ref, v.platform)

case image.TypeImageZip:
err = image.UnpackZip(context.Args()[0], context.Args()[1], v.ref, v.platform)

case image.TypeImage:
err = image.UnpackFile(context.Args()[0], context.Args()[1], v.ref, v.platform)

Expand Down
3 changes: 3 additions & 0 deletions cmd/oci-image-tool/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
var validateTypes = []string{
image.TypeImageLayout,
image.TypeImage,
image.TypeImageZip,
image.TypeManifest,
image.TypeImageIndex,
image.TypeConfig,
Expand Down Expand Up @@ -100,6 +101,8 @@ func validatePath(name string) error {
switch typ {
case image.TypeImageLayout:
return image.ValidateLayout(name, v.refs, v.stdout)
case image.TypeImageZip:
return image.ValidateZip(name, v.refs, v.stdout)
case image.TypeImage:
return image.ValidateFile(name, v.refs, v.stdout)
}
Expand Down
2 changes: 2 additions & 0 deletions completions/bash/oci-image-tool
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ __oci-image-tool_complete_common_types() {
COMPREPLY=( $( compgen -W "
image
imageLayout
imageZip
" -- "$cur" ) )
}

Expand All @@ -113,6 +114,7 @@ __oci-image-tool_complete_validate_types() {
image
imageIndex
imageLayout
imageZip
manifest
" -- "$cur" ) )
}
Expand Down
5 changes: 4 additions & 1 deletion image/autodetect.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
const (
TypeImageLayout = "imageLayout"
TypeImage = "image"
TypeImageZip = "imageZip"
TypeManifest = "manifest"
TypeImageIndex = "imageIndex"
TypeConfig = "config"
Expand Down Expand Up @@ -60,8 +61,10 @@ func Autodetect(path string) (string, error) {
mimeType := http.DetectContentType(buf)

switch mimeType {
case "application/x-gzip", "application/octet-stream":
case "application/x-gzip", "application/x-rar-compressed", "application/octet-stream":
return TypeImage, nil
case "application/zip":
return TypeImageZip, nil

case "text/plain; charset=utf-8":
// might be a JSON file, will be handled below
Expand Down
21 changes: 21 additions & 0 deletions image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ func ValidateLayout(src string, refs []string, out *log.Logger) error {
return validate(newPathWalker(src), refs, out)
}

// ValidateZip walks through the given file tree and validates the manifest
// pointed to by the given refs or returns an error if the validation failed.
func ValidateZip(src string, refs []string, out *log.Logger) error {
return validate(newZipWalker(src), refs, out)
}

// ValidateFile opens the tar file given by the filename, then calls ValidateReader
func ValidateFile(tarFile string, refs []string, out *log.Logger) error {
f, err := os.Open(tarFile)
Expand Down Expand Up @@ -135,6 +141,7 @@ func validate(w walker, refs []string, out *log.Logger) error {
out.Printf("reference %q: OK", ref)
}
}

return nil
}

Expand All @@ -145,6 +152,13 @@ func UnpackLayout(src, dest, ref, platform string) error {
return unpack(newPathWalker(src), dest, ref, platform)
}

// 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)
}

// UnpackFile opens the file pointed by tarFileName and calls Unpack on it.
func UnpackFile(tarFileName, dest, ref, platform string) error {
f, err := os.Open(tarFileName)
Expand Down Expand Up @@ -221,6 +235,13 @@ func CreateRuntimeBundleLayout(src, dest, ref, root, platform string) error {
return createRuntimeBundle(newPathWalker(src), dest, ref, root, platform)
}

// 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)
}

// CreateRuntimeBundleFile opens the file pointed by tarFile and calls
// CreateRuntimeBundle.
func CreateRuntimeBundleFile(tarFile, dest, ref, root, platform string) error {
Expand Down
7 changes: 4 additions & 3 deletions image/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/opencontainers/image-spec/schema"
Expand All @@ -32,7 +33,7 @@ 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") {
if strings.EqualFold(filepath.Base(path), "blobs") {
blobsExist = true
if !info.IsDir() {
return fmt.Errorf("blobs is not a directory")
Expand All @@ -41,7 +42,7 @@ func layoutValidate(w walker) error {
return nil
}

if strings.EqualFold(path, "index.json") {
if strings.EqualFold(filepath.Base(path), "index.json") {
indexExist = true
if info.IsDir() {
return fmt.Errorf("index.json is a directory")
Expand All @@ -60,7 +61,7 @@ func layoutValidate(w walker) error {
return nil
}

if strings.EqualFold(path, "oci-layout") {
if strings.EqualFold(filepath.Base(path), "oci-layout") {
layoutExist = true
if info.IsDir() {
return fmt.Errorf("oci-layout is a directory")
Expand Down
65 changes: 64 additions & 1 deletion image/walker.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package image

import (
"archive/tar"
"archive/zip"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -100,7 +101,7 @@ func (w *tarWalker) get(desc v1.Descriptor, dst io.Writer) (int64, error) {
return nil
}

if path == expectedPath && !info.IsDir() {
if filepath.Clean(path) == expectedPath && !info.IsDir() {
if bytes, err = io.Copy(dst, rdr); err != nil {
return errors.Wrapf(err, "get failed: failed to copy blob to destination")
}
Expand Down Expand Up @@ -186,3 +187,65 @@ func (w *pathWalker) get(desc v1.Descriptor, dst io.Writer) (int64, error) {
}
return nbytes, nil
}

type zipWalker struct {
fileName string
}

// newWalkWalker returns a Walker that walks through .zip files.
func newZipWalker(fileName string) walker {
return &zipWalker{fileName}
}

func (w *zipWalker) walk(f walkFunc) error {
r, err := zip.OpenReader(w.fileName)
if err != nil {
return err
}
defer r.Close()

for _, file := range r.File {
rc, err := file.Open()
if err != nil {
return err
}
defer rc.Close()
info := file.FileInfo()
if err := f(file.Name, info, rc); err != nil {
return err
}
}

return nil
}

func (w *zipWalker) get(desc v1.Descriptor, dst io.Writer) (int64, error) {
var bytes int64
done := false

expectedPath := filepath.Join("blobs", string(desc.Digest.Algorithm()), desc.Digest.Hex())

f := func(path string, info os.FileInfo, rdr io.Reader) error {
var err error
if done {
return nil
}

if path == expectedPath && !info.IsDir() {
if bytes, err = io.Copy(dst, rdr); err != nil {
return errors.Wrapf(err, "get failed: failed to copy blob to destination")
}
done = true
}
return nil
}

if err := w.walk(f); err != nil {
return 0, errors.Wrapf(err, "get failed: unable to walk")
}
if !done {
return 0, os.ErrNotExist
}

return bytes, nil
}
2 changes: 1 addition & 1 deletion man/oci-image-tool-create.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ runtime-spec-compatible `dest/config.json`.
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 "imageLayout,image,imageZip"

**--platform**=""
Specify the os and architecture of the manifest, format is OS:Architecture.
Expand Down
2 changes: 1 addition & 1 deletion man/oci-image-tool-unpack.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,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 "imageLayout,image,imageZip"

**--platform**=""
Specify the os and architecture of the manifest, format is OS:Architecture.
Expand Down
2 changes: 1 addition & 1 deletion man/oci-image-tool-validate.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ oci-image-tool validate \- Validate one or more image files
Only applicable if type is image or imageLayout.

**--type**=""
Type of the file to validate. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image,manifest,imageIndex,config"
Type of the file to validate. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image,imageZip,manifest,imageIndex,config"

# EXAMPLES
```
Expand Down