Update non-module dependencies

Dependabot was apparently not picking these up (and
several haven't had a release for a long time anyway).

Also move from github.com/go-check/check to its newly
declared (and go.mod-enforced) name gopkg.in/check.v1.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
This commit is contained in:
Miloslav Trmač
2021-08-23 15:36:56 +02:00
parent 5da1b0f304
commit c399909f04
43 changed files with 506 additions and 592 deletions

View File

@@ -14,9 +14,21 @@
"architecture": {
"type": "string"
},
"variant": {
"type": "string"
},
"os": {
"type": "string"
},
"os.version": {
"type": "string"
},
"os.features": {
"type": "array",
"items": {
"type": "string"
}
},
"config": {
"type": "object",
"properties": {

View File

@@ -15,10 +15,9 @@
package schema
import (
"bufio"
"encoding/json"
"io"
"go4.org/errorutil"
)
// A SyntaxError is a description of a JSON syntax error
@@ -36,7 +35,21 @@ func (e *SyntaxError) Error() string { return e.msg }
// If the given error is not a *json.SyntaxError it is returned unchanged.
func WrapSyntaxError(r io.Reader, err error) error {
if serr, ok := err.(*json.SyntaxError); ok {
line, col, _ := errorutil.HighlightBytePosition(r, serr.Offset)
buf := bufio.NewReader(r)
line := 0
col := 0
for i := int64(0); i < serr.Offset; i++ {
b, berr := buf.ReadByte()
if berr != nil {
break
}
if b == '\n' {
line++
col = 1
} else {
col++
}
}
return &SyntaxError{serr.Error(), line, col, serr.Offset}
}

View File

@@ -20,6 +20,8 @@ import (
"bytes"
"compress/gzip"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
@@ -114,7 +116,24 @@ func (f *_escFile) Close() error {
}
func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, nil
if !f.isDir {
return nil, fmt.Errorf(" escFile.Readdir: '%s' is not directory", f.name)
}
fis, ok := _escDirs[f.local]
if !ok {
return nil, fmt.Errorf(" escFile.Readdir: '%s' is directory, but we have no info about content of this dir, local=%s", f.name, f.local)
}
limit := count
if count <= 0 || limit > len(fis) {
limit = len(fis)
}
if len(fis) == 0 && count > 0 {
return nil, io.EOF
}
return fis[0:limit], nil
}
func (f *_escFile) Stat() (os.FileInfo, error) {
@@ -205,27 +224,29 @@ func _escFSMustString(useLocal bool, name string) string {
var _escData = map[string]*_escFile{
"/config-schema.json": {
name: "config-schema.json",
local: "config-schema.json",
size: 2771,
modtime: 1515512099,
size: 2969,
modtime: 1625865937,
compressed: `
H4sIAAAAAAAC/+RWQY/TPBC951dE2T22m+/wnXot3JCKVAGHFarcZNLOEnvMeIKIUP87itNCkjpp6apc
OEUaz7z35nns+EcUx0kOLmO0gmSSRZysLJglGVFogOMlmQJ38dpChgVmymfNmrJHl+1Bq6ZkL2IXafri
yMzb6BPxLs1ZFTL/7/+0jT20dZifStwiTcmCyU5szpe12SlqtYM08/xtpdQWmlravkAmbcwyWWBBcMki
btqJ4yRjUAL5r0Cn1AmjaeF8vCDWSpqVXAnMBTUkfu3QpiSqkj3xBFQ/m7M9CmRSMVxbQ+7azKMXgeyO
Iz4ecMXHPzjgXmSEscPqc95+t+Qgf08sblj/yFB4A6FwT80IPKQ5FGiwGRWXamXXHnnVagzjm29jshSz
qpNZdwkF9FDGRCNxfBghFa4toZEhNxlYNT099wj6dJMSJ2RekNqXO5A8qcJUZdlH6uJ8Dlqw1Pk/2/tH
KisN7sb+b536e3f1ifgLmt0bvOmcv1NbKO9tyTqw8fe0ZC1k17gzqrzakqj7PV2/TCSFe831m2NRbDB3
f/+uO+ZPdd+jBVPpsx1PSlUDuyTseDRgTRi+Vsj+P/wc8GCoLuoinjzfoxPiOmR636yAUWPbM75BwbfD
Zbem3hGB8T5/U1ze1FlA42ZbvwKDtIazP98fAIC2Um/8RIyDbIlKUGZkPvunLDoynM9N/1n1+9nUP5dR
MzuH6GcAAAD//0pj2wvTCgAA
H4sIAAAAAAAC/+RWsW7bQAzd9RWCkjGJOnTymnYrkAJG2yEojLNE2Ux1xyuPMioU/vdCJzvx2SfZteEu
XSnyvcdHSuLvJE2zElzBaAXJZJM0e7JgHsmIQgOcPpKpcJFOLRRYYaF81l1XduuKJWjVlSxF7CTPXxyZ
+z76QLzIS1aV3L97n/exm74Oy22Jm+Q5WTDFls35sj47R60WkBeev6+U1kJXS/MXKKSPWSYLLAgum6Rd
O2maFQxKoHwN7JQ6YTQ9nI9XxFpJ96RUAveCGjL/bN2nZKqRJfEIVJjNxRIFCmkYTq1ZKUZl5NR0cqdn
PqyAXT/WUysqUJ34KIliVu2bdyigd/MGwNN0HZBsJhrB35mrj0dm6+NfHHAQGWR+ZfU5H39ZclB+Jha3
X3/LUPk1gMo9dIt8k5dQocFu4V2ulZ165KdeYxzfrIZkhdYN2DfayNbGQ1Lh1hIGG9RP08BT19NzQBDS
jUockXlEaih3T/KoCtPUdYi0i/M9asGjLv/b3r9S3WhwZ/Z/7tZfu6tvxD/QLD7gWe/5JzWH+tqWTCOD
v6YlUyE7xYVR9cmWRD+/TCSVu+TzW2JVzbB0//5bt8kf6z6gBdPog4lntWqBXRZ3PNljzRh+Nsj+mniO
eLCvLtlF3Hq+RCfE7WX/1L3xDA8oegEdd2vsGoqs9+FldHyodxGNs3l7AQZpDQd/vr8AAG2lnfmNGAaZ
E9WgzMB+hm9ZsmE43JvwOHy75sL3Mul2Z538CQAA//9C38scmQsAAA==
`,
},
"/content-descriptor.json": {
name: "content-descriptor.json",
local: "content-descriptor.json",
size: 1079,
modtime: 1537191585,
modtime: 1625865919,
compressed: `
H4sIAAAAAAAC/5yTsW7cMAyGdz8F4QTIkos6BB2MIEu7d2i3ooNOok5Mz5JK8RBci7x7QcvX2G2RILfZ
xP+Rn2zqVwfQe6yOqQjl1A/QfyqYPuQklhIy6BMmgY9zKDN8LugokLMTca0tLquLOFrFo0gZjHmoOW1a
@@ -238,9 +259,10 @@ dIbaEm+G3WzZM/44EKMqff37riz3dL0uHcC37qn7HQAA//9DKIMKNwQAAA==
},
"/defs-descriptor.json": {
name: "defs-descriptor.json",
local: "defs-descriptor.json",
size: 844,
modtime: 1537191664,
modtime: 1625865919,
compressed: `
H4sIAAAAAAAC/5SST2/TTBDG7/kU826jt0DiOHBAqlWKKnrnUE6t0mi6O7aneP9od6IqVPnuaG03SYtA
cLC1+2jmefwbz9MEQBlKOnIQ9k5VoK6oZsf5liBgFNabDiOIh6+B3BfvBNlRhKuxzUe4DqS5Zo29x3ww
@@ -254,9 +276,10 @@ TAMAAA==
},
"/defs.json": {
name: "defs.json",
local: "defs.json",
size: 1670,
modtime: 1515512099,
modtime: 1625865903,
compressed: `
H4sIAAAAAAAC/7STza6bMBCF9zzFyO2S9oJtbGDb7hMpy6oLSiaJq2AjY6RWEe9e8RNChFuJKneRgGc8
3zmeMbcAgByxKa2qnTKa5EC+4klp1a8aaBs8grtY054vpnXgLgi7GvUXo12hNFo41FiqkyqLoTwceTOA
@@ -269,9 +292,10 @@ fIvD7in0ryMEy+fK1G6UfmdTE+tvpoL+1wV/AgAA//96IpqyhgYAAA==
},
"/image-index-schema.json": {
name: "image-index-schema.json",
local: "image-index-schema.json",
size: 2993,
modtime: 1515512099,
modtime: 1625865919,
compressed: `
H4sIAAAAAAAC/6yWz0/jOhDH7/0rRgGJC5CnJ/QOFeLy9sJpD4v2suJg7EkybGNnx1Ogu+r/vrJN2qRJ
C4Te2rHnO5/vxL/+zAAyg14zNULOZnPIvjZo/3dWFFlkuK1ViXBrDb7AtwY1FaRVnHoeck+9rrBWIa8S
@@ -289,9 +313,10 @@ VmZjL8HOE24GcD9bz/4GAAD//yCnv52xCwAA
},
"/image-layout-schema.json": {
name: "image-layout-schema.json",
local: "image-layout-schema.json",
size: 439,
modtime: 1515512099,
modtime: 1625865903,
compressed: `
H4sIAAAAAAAC/2yPQUvEMBCF7/0VQ/Sg4DYVPOW6pwVhD4IX8VDTaTvLNonJVFik/12SaRXRU5g38+W9
91kBqA6TjRSYvFMG1DGg23vHLTmMcJjaAeGxvfiZ4cmOOLXqLlPXSQYDamQORutT8m4nau3joLvY9rxr
@@ -302,9 +327,10 @@ HrRoV8JRtyHJaO0DOruZpYLJtaZsrM/FWEi+BMysfzuhXbUQfcDIhEkZyG2yQyYl8TPGJLVk97fth1yA
},
"/image-manifest-schema.json": {
name: "image-manifest-schema.json",
local: "image-manifest-schema.json",
size: 921,
modtime: 1515512099,
modtime: 1625865903,
compressed: `
H4sIAAAAAAAC/5ySMW8iMRCF+/0VI0MJ+O501bZXUZxSJEoTpXB2x7uDWNsZmygo4r9HtnHAkCKifTvv
zTdv/dEAiB59x+QCWSNaEHcOzT9rgiKDDOtJDQj/lSGNPsC9w440dSpNL6J97rsRJxWtYwiulXLjrVlm
@@ -317,7 +343,21 @@ Dj+ZAwAA
},
"/": {
name: "/",
local: `.`,
isDir: true,
local: "",
},
}
var _escDirs = map[string][]os.FileInfo{
".": {
_escData["/config-schema.json"],
_escData["/content-descriptor.json"],
_escData["/defs-descriptor.json"],
_escData["/defs.json"],
_escData["/image-index-schema.json"],
_escData["/image-layout-schema.json"],
_escData["/image-manifest-schema.json"],
},
}

View File

@@ -23,7 +23,7 @@ import (
"regexp"
digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/xeipuuv/gojsonschema"
)
@@ -168,6 +168,7 @@ func validateIndex(r io.Reader) error {
}
if manifest.Platform != nil {
checkPlatform(manifest.Platform.OS, manifest.Platform.Architecture)
checkArchitecture(manifest.Platform.Architecture, manifest.Platform.Variant)
}
}
@@ -189,6 +190,7 @@ func validateConfig(r io.Reader) error {
}
checkPlatform(header.OS, header.Architecture)
checkArchitecture(header.Architecture, header.Variant)
envRegexp := regexp.MustCompile(`^[^=]+=.*$`)
for _, e := range header.Config.Env {
@@ -200,6 +202,31 @@ func validateConfig(r io.Reader) error {
return nil
}
func checkArchitecture(Architecture string, Variant string) {
validCombins := map[string][]string{
"arm": {"", "v6", "v7", "v8"},
"arm64": {"", "v8"},
"386": {""},
"amd64": {""},
"ppc64": {""},
"ppc64le": {""},
"mips64": {""},
"mips64le": {""},
"s390x": {""},
}
for arch, variants := range validCombins {
if arch == Architecture {
for _, variant := range variants {
if variant == Variant {
return
}
}
fmt.Printf("warning: combination of architecture %q and variant %q is not valid.\n", Architecture, Variant)
}
}
fmt.Printf("warning: architecture %q is not supported yet.\n", Architecture)
}
func checkPlatform(OS string, Architecture string) {
validCombins := map[string][]string{
"android": {"arm"},
@@ -219,7 +246,7 @@ func checkPlatform(OS string, Architecture string) {
return
}
}
fmt.Printf("warning: combination of %q and %q is invalid.\n", OS, Architecture)
fmt.Printf("warning: combination of os %q and architecture %q is invalid.\n", OS, Architecture)
}
}
fmt.Printf("warning: operating system %q of the bundle is not supported yet.\n", OS)

View File

@@ -53,4 +53,10 @@ const (
// AnnotationDescription is the annotation key for the human-readable description of the software packaged in the image.
AnnotationDescription = "org.opencontainers.image.description"
// AnnotationBaseImageDigest is the annotation key for the digest of the image's base image.
AnnotationBaseImageDigest = "org.opencontainers.image.base.digest"
// AnnotationBaseImageName is the annotation key for the image reference of the image's base image.
AnnotationBaseImageName = "org.opencontainers.image.base.name"
)

View File

@@ -89,9 +89,20 @@ type Image struct {
// Architecture is the CPU architecture which the binaries in this image are built to run on.
Architecture string `json:"architecture"`
// Variant is the variant of the specified CPU architecture which image binaries are intended to run on.
Variant string `json:"variant,omitempty"`
// OS is the name of the operating system which the image is built to run on.
OS string `json:"os"`
// OSVersion is an optional field specifying the operating system
// version, for example on Windows `10.0.14393.1066`.
OSVersion string `json:"os.version,omitempty"`
// OSFeatures is an optional field specifying an array of strings,
// each listing a required OS feature (for example on Windows `win32k`).
OSFeatures []string `json:"os.features,omitempty"`
// Config defines the execution parameters which should be used as a base when running a container using the image.
Config ImageConfig `json:"config,omitempty"`

View File

@@ -15,13 +15,11 @@
package image
import (
"encoding/json"
"io"
"io/ioutil"
"net/http"
"os"
"github.com/opencontainers/image-spec/schema"
"github.com/pkg/errors"
)
@@ -47,7 +45,7 @@ func Autodetect(path string) (string, error) {
return TypeImageLayout, nil
}
f, err := os.Open(path)
f, err := os.Open(path) // nolint: errcheck, gosec
if err != nil {
return "", errors.Wrap(err, "unable to open file") // os.Open includes the filename
}
@@ -65,48 +63,7 @@ func Autodetect(path string) (string, error) {
return TypeImage, nil
case "application/zip":
return TypeImageZip, nil
case "text/plain; charset=utf-8":
// might be a JSON file, will be handled below
default:
return "", errors.New("unknown file type")
}
if _, err := f.Seek(0, io.SeekStart); err != nil {
return "", errors.Wrap(err, "unable to seek")
}
header := struct {
SchemaVersion int `json:"schemaVersion"`
MediaType string `json:"mediaType"`
Config interface{} `json:"config"`
}{}
if err := json.NewDecoder(f).Decode(&header); err != nil {
if _, errSeek := f.Seek(0, io.SeekStart); errSeek != nil {
return "", errors.Wrap(err, "unable to seek")
}
e := errors.Wrap(
schema.WrapSyntaxError(f, err),
"unable to parse JSON",
)
return "", e
}
switch {
case header.MediaType == string(schema.ValidatorMediaTypeManifest):
return TypeManifest, nil
case header.MediaType == string(schema.ValidatorMediaTypeImageIndex):
return TypeImageIndex, nil
case header.MediaType == "" && header.SchemaVersion == 0 && header.Config != nil:
// config files don't have mediaType/schemaVersion header
return TypeConfig, nil
}
return "", errors.New("unknown media type")
return "", errors.New("unknown file type")
}

View File

@@ -20,7 +20,6 @@ import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
@@ -31,16 +30,11 @@ import (
"github.com/pkg/errors"
)
type config v1.Image
func findConfig(w walker, d *v1.Descriptor) (*config, error) {
var c config
func findConfig(w walker, d *v1.Descriptor) (*v1.Image, error) {
var c v1.Image
cpath := 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) != cpath {
return nil
}
switch err := w.find(cpath, func(path string, r io.Reader) error {
buf, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrapf(err, "%s: error reading config", path)
@@ -65,7 +59,7 @@ func findConfig(w walker, d *v1.Descriptor) (*config, error) {
}
}
func (c *config) runtimeSpec(rootfs string) (*specs.Spec, error) {
func runtimeSpec(c *v1.Image, rootfs string) (*specs.Spec, error) {
if c.OS != "linux" {
return nil, fmt.Errorf("%s: unsupported OS", c.OS)
}

View File

@@ -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,10 +40,54 @@ func listReferences(w walker) (map[string]*v1.Descriptor, error) {
if err := json.NewDecoder(r).Decode(&index); err != nil {
return err
}
descs = index.Manifests
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]
return nil
}); err != nil {
return nil, err
}
return descs, nil
}
func findDescriptor(w walker, names []string) ([]v1.Descriptor, error) {
var descs []v1.Descriptor
var index v1.Index
dpath := "index.json"
if err := w.find(dpath, func(path string, r io.Reader) error {
if err := json.NewDecoder(r).Decode(&index); err != nil {
return err
}
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])
}
}
@@ -50,38 +95,14 @@ func listReferences(w walker) (map[string]*v1.Descriptor, error) {
}); err != nil {
return nil, err
}
return refs, nil
}
func findDescriptor(w walker, name string) (*v1.Descriptor, error) {
var d v1.Descriptor
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 {
return nil
}
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] == name {
d = index.Manifests[i]
return errEOW
}
}
return nil
}); err {
case nil:
return nil, fmt.Errorf("index.json: descriptor %q not found", name)
case errEOW:
return &d, nil
default:
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 {

View File

@@ -12,5 +12,5 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package image defines methods for validating, and unpacking OCI images.
// Package image defines methods for validating, unpacking OCI images and creating OCI runtime bundle.
package image

View File

@@ -41,7 +41,7 @@ func ValidateZip(src string, refs []string, out *log.Logger) error {
// 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)
f, err := os.Open(tarFile) // nolint: errcheck, gosec
if err != nil {
return errors.Wrap(err, "unable to open file")
}
@@ -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
}
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 _, 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)
}
for _, desc := range descs {
d := &desc
if err = validateDescriptor(d, w, validRefMediaTypes); err != nil {
return err
}
@@ -105,7 +104,7 @@ func validate(w walker, refs []string, out *log.Logger) error {
return err
}
if err := m.validate(w); err != nil {
if err := validateManifest(m, w); err != nil {
return err
}
}
@@ -131,15 +130,15 @@ func validate(w walker, refs []string, out *log.Logger) error {
return err
}
if err := m.validate(w); err != nil {
if err := validateManifest(m, w); err != nil {
return err
}
}
}
}
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 {
f, err := os.Open(tarFileName)
func UnpackFile(tarFileName, dest, platform string, refs []string) error {
f, err := os.Open(tarFileName) // nolint: errcheck, gosec
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
}
@@ -198,11 +198,11 @@ func unpack(w walker, dest, refName, platform string) error {
return err
}
if err := m.validate(w); err != nil {
if err := validateManifest(m, w); err != nil {
return err
}
return m.unpack(w, dest)
return unpackManifest(m, w, dest)
}
if ref.MediaType == validRefMediaTypes[1] {
@@ -221,7 +221,7 @@ func unpack(w walker, dest, refName, platform string) error {
}
for _, m := range manifests {
return m.unpack(w, dest)
return unpackManifest(m, w, dest)
}
}
@@ -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 {
f, err := os.Open(tarFile)
func CreateRuntimeBundleFile(tarFile, dest, root, platform string, refs []string) error {
f, err := os.Open(tarFile) // nolint: errcheck, gosec
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
}
@@ -281,7 +282,7 @@ func createRuntimeBundle(w walker, dest, refName, rootfs, platform string) error
return err
}
if err := m.validate(w); err != nil {
if err := validateManifest(m, w); err != nil {
return err
}
@@ -311,7 +312,7 @@ func createRuntimeBundle(w walker, dest, refName, rootfs, platform string) error
return nil
}
func createBundle(w walker, m *manifest, dest, rootfs string) (retErr error) {
func createBundle(w walker, m *v1.Manifest, dest, rootfs string) (retErr error) {
c, err := findConfig(w, &m.Config)
if err != nil {
return err
@@ -319,7 +320,7 @@ func createBundle(w walker, m *manifest, dest, rootfs string) (retErr error) {
if _, err = os.Stat(dest); err != nil {
if os.IsNotExist(err) {
if err2 := os.MkdirAll(dest, 0755); err2 != nil {
if err2 := os.MkdirAll(dest, 0750); err2 != nil {
return err2
}
defer func() {
@@ -334,11 +335,11 @@ func createBundle(w walker, m *manifest, dest, rootfs string) (retErr error) {
}
}
if err = m.unpack(w, filepath.Join(dest, rootfs)); err != nil {
if err = unpackManifest(m, w, filepath.Join(dest, rootfs)); err != nil {
return err
}
spec, err := c.runtimeSpec(rootfs)
spec, err := runtimeSpec(c, rootfs)
if err != nil {
return err
}
@@ -353,8 +354,8 @@ func createBundle(w walker, m *manifest, dest, rootfs string) (retErr error) {
}
// filertManifest returns a filtered list of manifests
func filterManifest(w walker, Manifests []v1.Descriptor, platform string) ([]*manifest, error) {
var manifests []*manifest
func filterManifest(w walker, Manifests []v1.Descriptor, platform string) ([]*v1.Manifest, error) {
var manifests []*v1.Manifest
argsParts := strings.Split(platform, ":")
if len(argsParts) != 2 {
@@ -372,7 +373,7 @@ func filterManifest(w walker, Manifests []v1.Descriptor, platform string) ([]*ma
return manifests, err
}
if err := m.validate(w); err != nil {
if err := validateManifest(m, w); err != nil {
return manifests, err
}
if strings.EqualFold(manifest.Platform.OS, argsParts[0]) && strings.EqualFold(manifest.Platform.Architecture, argsParts[1]) {
@@ -381,7 +382,7 @@ func filterManifest(w walker, Manifests []v1.Descriptor, platform string) ([]*ma
}
if len(manifests) == 0 {
return manifests, fmt.Errorf("There is no matching manifest")
return manifests, fmt.Errorf("there is no matching manifest")
}
return manifests, nil

View File

@@ -48,14 +48,13 @@ func layoutValidate(w walker) error {
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")
if err := schema.ValidatorMediaTypeImageIndex.Validate(bytes.NewReader(buf)); err != nil {
return errors.Wrap(err, "index.json validation failed")
}
return nil
@@ -74,7 +73,7 @@ func layoutValidate(w walker) error {
}
if err := schema.ValidatorMediaTypeLayoutHeader.Validate(bytes.NewReader(buf)); err != nil {
return errors.Wrap(err, "oci-layout: imageLayout validation failed")
return errors.Wrap(err, "oci-layout validation failed")
}
if err := json.Unmarshal(buf, &imageLayout); err != nil {

View File

@@ -29,26 +29,17 @@ import (
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/opencontainers/image-spec/schema"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type manifest struct {
Config v1.Descriptor `json:"config"`
Layers []v1.Descriptor `json:"layers"`
}
func findManifest(w walker, d *v1.Descriptor) (*manifest, error) {
var m manifest
func findManifest(w walker, d *v1.Descriptor) (*v1.Manifest, error) {
var m v1.Manifest
mpath := 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) != mpath {
return nil
}
switch err := w.find(mpath, func(path string, r io.Reader) error {
buf, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrapf(err, "%s: error reading manifest", path)
@@ -73,7 +64,7 @@ func findManifest(w walker, d *v1.Descriptor) (*manifest, error) {
}
}
func (m *manifest) validate(w walker) error {
func validateManifest(m *v1.Manifest, w walker) error {
if err := validateDescriptor(&m.Config, w, []string{v1.MediaTypeImageConfig}); err != nil {
return errors.Wrap(err, "config validation failed")
}
@@ -94,7 +85,7 @@ func (m *manifest) validate(w walker) error {
return nil
}
func (m *manifest) unpack(w walker, dest string) (retErr error) {
func unpackManifest(m *v1.Manifest, w walker, dest string) (retErr error) {
// error out if the dest directory is not empty
s, err := ioutil.ReadDir(dest)
if err != nil && !os.IsNotExist(err) { // We'll create the dir later
@@ -113,18 +104,10 @@ func (m *manifest) unpack(w walker, dest string) (retErr error) {
}
}()
for _, d := range m.Layers {
switch err := w.walk(func(path string, info os.FileInfo, r io.Reader) error {
if info.IsDir() {
return nil
}
dd, err := filepath.Rel(filepath.Join("blobs", string(d.Digest.Algorithm())), filepath.Clean(path))
if err != nil || d.Digest.Hex() != dd {
return nil
}
lpath := filepath.Join("blobs", string(d.Digest.Algorithm()), d.Digest.Hex())
switch err := w.find(lpath, func(path string, r io.Reader) error {
if err := unpackLayer(d.MediaType, path, dest, r); err != nil {
return errors.Wrap(err, "error unpack: extracting layer")
return errors.Wrap(err, "unpack: error extracting layer")
}
return errEOW
@@ -218,85 +201,15 @@ loop:
return errors.Wrapf(err, "error advancing tar stream")
}
hdr.Name = filepath.Clean(hdr.Name)
if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) {
// Not the root directory, ensure that the parent directory exists
parent := filepath.Dir(hdr.Name)
parentPath := filepath.Join(dest, parent)
if _, err2 := os.Lstat(parentPath); err2 != nil && os.IsNotExist(err2) {
if err3 := os.MkdirAll(parentPath, 0755); err3 != nil {
return err3
}
}
}
path := filepath.Join(dest, hdr.Name)
if entries[path] {
return fmt.Errorf("duplicate entry for %s", path)
}
entries[path] = true
rel, err := filepath.Rel(dest, path)
var whiteout bool
whiteout, err = unpackLayerEntry(dest, hdr, tr, &entries)
if err != nil {
return err
}
info := hdr.FileInfo()
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
return fmt.Errorf("%q is outside of %q", hdr.Name, dest)
}
if strings.HasPrefix(info.Name(), ".wh.") {
path = strings.Replace(path, ".wh.", "", 1)
if err := os.RemoveAll(path); err != nil {
return errors.Wrap(err, "unable to delete whiteout path")
}
if whiteout {
continue loop
}
switch hdr.Typeflag {
case tar.TypeDir:
if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) {
if err2 := os.MkdirAll(path, info.Mode()); err2 != nil {
return errors.Wrap(err2, "error creating directory")
}
}
case tar.TypeReg, tar.TypeRegA:
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, info.Mode())
if err != nil {
return errors.Wrap(err, "unable to open file")
}
if _, err := io.Copy(f, tr); err != nil {
f.Close()
return errors.Wrap(err, "unable to copy")
}
f.Close()
case tar.TypeLink:
target := filepath.Join(dest, hdr.Linkname)
if !strings.HasPrefix(target, dest) {
return fmt.Errorf("invalid hardlink %q -> %q", target, hdr.Linkname)
}
if err := os.Link(target, path); err != nil {
return err
}
case tar.TypeSymlink:
target := filepath.Join(filepath.Dir(path), hdr.Linkname)
if !strings.HasPrefix(target, dest) {
return fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname)
}
if err := os.Symlink(hdr.Linkname, path); err != nil {
return err
}
case tar.TypeXGlobalHeader:
return nil
}
// Directory mtimes must be handled at the end to avoid further
// file creation in them to modify the directory mtime
if hdr.Typeflag == tar.TypeDir {
@@ -315,3 +228,104 @@ loop:
}
return nil
}
// unpackLayerEntry unpacks a single entry from a layer.
func unpackLayerEntry(dest string, header *tar.Header, reader io.Reader, entries *map[string]bool) (whiteout bool, err error) {
header.Name = filepath.Clean(header.Name)
if !strings.HasSuffix(header.Name, string(os.PathSeparator)) {
// Not the root directory, ensure that the parent directory exists
parent := filepath.Dir(header.Name)
parentPath := filepath.Join(dest, parent)
if _, err2 := os.Lstat(parentPath); err2 != nil && os.IsNotExist(err2) {
if err3 := os.MkdirAll(parentPath, 0750); err3 != nil {
return false, err3
}
}
}
path := filepath.Join(dest, header.Name)
if (*entries)[path] {
return false, fmt.Errorf("duplicate entry for %s", path)
}
(*entries)[path] = true
rel, err := filepath.Rel(dest, path)
if err != nil {
return false, err
}
info := header.FileInfo()
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
return false, fmt.Errorf("%q is outside of %q", header.Name, dest)
}
if strings.HasPrefix(info.Name(), ".wh.") {
path = strings.Replace(path, ".wh.", "", 1)
if err = os.RemoveAll(path); err != nil {
return true, errors.Wrap(err, "unable to delete whiteout path")
}
return true, nil
}
if header.Typeflag != tar.TypeDir {
err = os.RemoveAll(path)
if err != nil && !os.IsNotExist(err) {
return false, err
}
}
switch header.Typeflag {
case tar.TypeDir:
fi, err := os.Lstat(path)
if err != nil && !os.IsNotExist(err) {
return false, err
}
if os.IsNotExist(err) || !fi.IsDir() {
err = os.RemoveAll(path)
if err != nil && !os.IsNotExist(err) {
return false, err
}
err = os.MkdirAll(path, info.Mode())
if err != nil {
return false, err
}
}
case tar.TypeReg, tar.TypeRegA:
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, info.Mode())
if err != nil {
return false, errors.Wrap(err, "unable to open file")
}
if _, err := io.Copy(f, reader); err != nil {
defer f.Close()
return false, errors.Wrap(err, "unable to copy")
}
defer f.Close()
case tar.TypeLink:
target := filepath.Join(dest, header.Linkname)
if !strings.HasPrefix(target, dest) {
return false, fmt.Errorf("invalid hardlink %q -> %q", target, header.Linkname)
}
if err := os.Link(target, path); err != nil {
return false, err
}
case tar.TypeSymlink:
target := filepath.Join(filepath.Dir(path), header.Linkname)
if !strings.HasPrefix(target, dest) {
return false, fmt.Errorf("invalid symlink %q -> %q", path, header.Linkname)
}
if err := os.Symlink(header.Linkname, path); err != nil {
return false, err
}
case tar.TypeXGlobalHeader:
return false, nil
}
return false, nil
}

View File

@@ -34,6 +34,8 @@ var (
// walkFunc is a function type that gets called for each file or directory visited by the Walker.
type walkFunc func(path string, _ os.FileInfo, _ io.Reader) error
type findFunc func(path string, r io.Reader) error
// walker is the interface that defines how to access a given archival format
type walker interface {
@@ -43,6 +45,9 @@ type walker interface {
// get will copy an arbitrary blob, defined by desc, in to dst. returns
// the number of bytes copied on success.
get(desc v1.Descriptor, dst io.Writer) (int64, error)
// find calls findFunc for handling content of path
find(path string, ff findFunc) error
}
// tarWalker exposes access to image layouts in a tar file.
@@ -120,6 +125,34 @@ func (w *tarWalker) get(desc v1.Descriptor, dst io.Writer) (int64, error) {
return bytes, nil
}
func (w *tarWalker) find(path string, ff findFunc) error {
done := false
f := func(relpath string, info os.FileInfo, rdr io.Reader) error {
var err error
if done {
return nil
}
if filepath.Clean(relpath) == path && !info.IsDir() {
if err = ff(relpath, rdr); err != nil {
return err
}
done = true
}
return nil
}
if err := w.walk(f); err != nil {
return errors.Wrapf(err, "find failed: unable to walk")
}
if !done {
return os.ErrNotExist
}
return nil
}
type eofReader struct{}
func (eofReader) Read(_ []byte) (int, error) {
@@ -153,7 +186,7 @@ func (w *pathWalker) walk(f walkFunc) error {
return f(rel, info, eofReader{})
}
file, err := os.Open(path)
file, err := os.Open(path) // nolint: errcheck, gosec
if err != nil {
return errors.Wrap(err, "unable to open file") // os.Open includes the path
}
@@ -175,7 +208,7 @@ func (w *pathWalker) get(desc v1.Descriptor, dst io.Writer) (int64, error) {
return 0, fmt.Errorf("object is dir")
}
fp, err := os.Open(name)
fp, err := os.Open(name) // nolint: errcheck, gosec
if err != nil {
return 0, errors.Wrapf(err, "get failed")
}
@@ -188,6 +221,27 @@ func (w *pathWalker) get(desc v1.Descriptor, dst io.Writer) (int64, error) {
return nbytes, nil
}
func (w *pathWalker) find(path string, ff findFunc) error {
name := filepath.Join(w.root, path)
info, err := os.Stat(name)
if err != nil {
return err
}
if info.IsDir() {
return fmt.Errorf("object is dir")
}
file, err := os.Open(name) // nolint: errcheck, gosec
if err != nil {
return errors.Wrap(err, "unable to open file") // os.Open includes the path
}
defer file.Close()
return ff(name, file)
}
type zipWalker struct {
fileName string
}
@@ -249,3 +303,31 @@ func (w *zipWalker) get(desc v1.Descriptor, dst io.Writer) (int64, error) {
return bytes, nil
}
func (w *zipWalker) find(path string, ff findFunc) error {
done := false
f := func(relpath string, info os.FileInfo, rdr io.Reader) error {
var err error
if done {
return nil
}
if filepath.Clean(relpath) == path && !info.IsDir() {
if err = ff(relpath, rdr); err != nil {
return err
}
done = true
}
return nil
}
if err := w.walk(f); err != nil {
return errors.Wrapf(err, "find failed: unable to walk")
}
if !done {
return os.ErrNotExist
}
return nil
}