From 8310fb4bfd84b8efc44d318e4de9f8e3cb5fad3c Mon Sep 17 00:00:00 2001 From: Mike Brown Date: Wed, 13 Sep 2017 18:12:37 -0500 Subject: [PATCH] update vendor Signed-off-by: Mike Brown --- hack/versions | 6 +- vendor.conf | 10 +- .../containerd/containerd/container_opts.go | 5 +- .../containerd/container_opts_unix.go | 3 +- .../containerd/containerd/errdefs/errors.go | 6 +- .../containerd/containerd/errdefs/grpc.go | 4 +- .../github.com/containerd/containerd/image.go | 23 +- .../containerd/containerd/images/handlers.go | 2 +- .../containerd/containerd/images/image.go | 138 +++++- .../containerd/platforms/database.go | 77 +++ .../containerd/platforms/defaults.go | 16 + .../containerd/platforms/platforms.go | 236 +++++++++ .../containerd/services/diff/service.go | 4 +- .../containerd/containerd/spec_opts_unix.go | 5 +- .../containerd/spec_opts_windows.go | 3 +- .../containerd/containerd/spec_unix.go | 10 +- .../containerd/containerd/vendor.conf | 2 +- vendor/github.com/hashicorp/errwrap/LICENSE | 354 ++++++++++++++ vendor/github.com/hashicorp/errwrap/README.md | 89 ++++ .../github.com/hashicorp/errwrap/errwrap.go | 169 +++++++ .../hashicorp/go-multierror/LICENSE | 353 ++++++++++++++ .../hashicorp/go-multierror/README.md | 97 ++++ .../hashicorp/go-multierror/append.go | 41 ++ .../hashicorp/go-multierror/flatten.go | 26 + .../hashicorp/go-multierror/format.go | 27 ++ .../hashicorp/go-multierror/multierror.go | 51 ++ .../hashicorp/go-multierror/prefix.go | 37 ++ .../runtime-tools/error/error.go | 92 ++++ .../runtime-tools/generate/generate.go | 177 ++++++- .../runtime-tools/generate/spec.go | 7 + .../runtime-tools/specerror/error.go | 170 +++++++ .../runtime-tools/validate/error.go | 110 ----- .../runtime-tools/validate/validate.go | 458 +++++++++++++----- 33 files changed, 2500 insertions(+), 308 deletions(-) create mode 100644 vendor/github.com/containerd/containerd/platforms/database.go create mode 100644 vendor/github.com/containerd/containerd/platforms/defaults.go create mode 100644 vendor/github.com/containerd/containerd/platforms/platforms.go create mode 100644 vendor/github.com/hashicorp/errwrap/LICENSE create mode 100644 vendor/github.com/hashicorp/errwrap/README.md create mode 100644 vendor/github.com/hashicorp/errwrap/errwrap.go create mode 100644 vendor/github.com/hashicorp/go-multierror/LICENSE create mode 100644 vendor/github.com/hashicorp/go-multierror/README.md create mode 100644 vendor/github.com/hashicorp/go-multierror/append.go create mode 100644 vendor/github.com/hashicorp/go-multierror/flatten.go create mode 100644 vendor/github.com/hashicorp/go-multierror/format.go create mode 100644 vendor/github.com/hashicorp/go-multierror/multierror.go create mode 100644 vendor/github.com/hashicorp/go-multierror/prefix.go create mode 100644 vendor/github.com/opencontainers/runtime-tools/error/error.go create mode 100644 vendor/github.com/opencontainers/runtime-tools/specerror/error.go delete mode 100644 vendor/github.com/opencontainers/runtime-tools/validate/error.go diff --git a/hack/versions b/hack/versions index ad984ef46..fee09adee 100644 --- a/hack/versions +++ b/hack/versions @@ -1,5 +1,5 @@ -RUNC_VERSION=e775f0fba3ea329b8b766451c892c41a3d49594d +RUNC_VERSION=593914b8bd5448a93f7c3e4902a03408b6d5c0ce CNI_VERSION=v0.6.0 -CONTAINERD_VERSION=v1.0.0-beta.0 -CRITEST_VERSION=d452f7fe9ef7ccc5ec63a8306cf838510cb83441 +CONTAINERD_VERSION=9f28040426990a8a640019b2a69b4cacb6991c01 +CRITEST_VERSION=3028b837818133b4e83758cf9aee867f0a3ff6d2 KUBERNETES_VERSION=11a836078d0c78a4253a77a3ff6f4a555c4121f9 diff --git a/vendor.conf b/vendor.conf index 1e7065f5c..6a9d1b088 100644 --- a/vendor.conf +++ b/vendor.conf @@ -1,11 +1,11 @@ github.com/blang/semver v3.1.0 -github.com/boltdb/bolt v1.3.0-58-ge9cf4fa +github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895 -github.com/containerd/containerd v1.0.0-beta.0 +github.com/containerd/containerd 9f28040426990a8a640019b2a69b4cacb6991c01 github.com/containerd/continuity cf279e6ac893682272b4479d4c67fd3abf878b4e github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6 github.com/containerd/cgroups 5933ab4dc4f7caa3a73a1dc141bd11f42b5c9163 -github.com/coreos/go-systemd d2196463941895ee908e13531a23a39feb9e1243 +github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6 github.com/containernetworking/cni v0.6.0 github.com/containernetworking/plugins v0.6.0 github.com/cri-o/ocicni 73f1309d6bc5c3eac78c1382408921cd771ff22e @@ -27,6 +27,8 @@ github.com/go-openapi/jsonreference 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 github.com/go-openapi/spec 6aced65f8501fe1217321abf0749d354824ba2ff github.com/go-openapi/swag 1d0bd113de87027671077d3c71eb3ac5d7dbba72 github.com/godbus/dbus 97646858c46433e4afb3432ad28c12e968efa298 +github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55 +github.com/hashicorp/go-multierror ed905158d87462226a13fe39ddf685ea65f1c11f github.com/juju/ratelimit 5b9ff866471762aa2ab2dced63c9fb6f53921342 github.com/mailru/easyjson d5b7844b561a7bc640052f1b935f7b800330d7e0 github.com/Microsoft/go-winio v0.4.4 @@ -34,7 +36,7 @@ github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448 github.com/opencontainers/image-spec v1.0.0 github.com/opencontainers/runc e775f0fba3ea329b8b766451c892c41a3d49594d github.com/opencontainers/runtime-spec v1.0.0 -github.com/opencontainers/runtime-tools e29f3ca4eb806a582ee1a1864c7b0563bd64c19b +github.com/opencontainers/runtime-tools 6073aff4ac61897f75895123f7e24135204a404d github.com/opencontainers/selinux 4a2974bf1ee960774ffd517717f1f45325af0206 github.com/pkg/errors v0.8.0 github.com/pmezard/go-difflib v1.0.0 diff --git a/vendor/github.com/containerd/containerd/container_opts.go b/vendor/github.com/containerd/containerd/container_opts.go index 008adfca2..10b566e18 100644 --- a/vendor/github.com/containerd/containerd/container_opts.go +++ b/vendor/github.com/containerd/containerd/container_opts.go @@ -5,6 +5,7 @@ import ( "github.com/containerd/containerd/containers" "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/typeurl" "github.com/gogo/protobuf/types" "github.com/opencontainers/image-spec/identity" @@ -79,7 +80,7 @@ func WithSnapshot(id string) NewContainerOpts { // root filesystem in read-write mode func WithNewSnapshot(id string, i Image) NewContainerOpts { return func(ctx context.Context, client *Client, c *containers.Container) error { - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore()) + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Format(platforms.Default())) if err != nil { return err } @@ -108,7 +109,7 @@ func WithSnapshotCleanup(ctx context.Context, client *Client, c containers.Conta // root filesystem in read-only mode func WithNewSnapshotView(id string, i Image) NewContainerOpts { return func(ctx context.Context, client *Client, c *containers.Container) error { - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore()) + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Format(platforms.Default())) if err != nil { return err } diff --git a/vendor/github.com/containerd/containerd/container_opts_unix.go b/vendor/github.com/containerd/containerd/container_opts_unix.go index 2e06b3a1c..8108f4b83 100644 --- a/vendor/github.com/containerd/containerd/container_opts_unix.go +++ b/vendor/github.com/containerd/containerd/container_opts_unix.go @@ -12,6 +12,7 @@ import ( "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" "github.com/gogo/protobuf/proto" protobuf "github.com/gogo/protobuf/types" digest "github.com/opencontainers/go-digest" @@ -38,7 +39,7 @@ func WithCheckpoint(desc v1.Descriptor, snapshotKey string) NewContainerOpts { fk := m rw = &fk case images.MediaTypeDockerSchema2Manifest: - config, err := images.Config(ctx, store, m) + config, err := images.Config(ctx, store, m, platforms.Format(platforms.Default())) if err != nil { return err } diff --git a/vendor/github.com/containerd/containerd/errdefs/errors.go b/vendor/github.com/containerd/containerd/errdefs/errors.go index c8f0a6c46..44ec72e7b 100644 --- a/vendor/github.com/containerd/containerd/errdefs/errors.go +++ b/vendor/github.com/containerd/containerd/errdefs/errors.go @@ -26,7 +26,7 @@ var ( ErrAlreadyExists = errors.New("already exists") ErrFailedPrecondition = errors.New("failed precondition") ErrUnavailable = errors.New("unavailable") - ErrNotSupported = errors.New("not supported") // represents not supported and unimplemented + ErrNotImplemented = errors.New("not implemented") // represents not supported and unimplemented ) func IsInvalidArgument(err error) bool { @@ -54,6 +54,6 @@ func IsUnavailable(err error) bool { return errors.Cause(err) == ErrUnavailable } -func IsNotSupported(err error) bool { - return errors.Cause(err) == ErrNotSupported +func IsNotImplemented(err error) bool { + return errors.Cause(err) == ErrNotImplemented } diff --git a/vendor/github.com/containerd/containerd/errdefs/grpc.go b/vendor/github.com/containerd/containerd/errdefs/grpc.go index 53e7ee2d4..76fb3eb3f 100644 --- a/vendor/github.com/containerd/containerd/errdefs/grpc.go +++ b/vendor/github.com/containerd/containerd/errdefs/grpc.go @@ -38,7 +38,7 @@ func ToGRPC(err error) error { return grpc.Errorf(codes.FailedPrecondition, err.Error()) case IsUnavailable(err): return grpc.Errorf(codes.Unavailable, err.Error()) - case IsNotSupported(err): + case IsNotImplemented(err): return grpc.Errorf(codes.Unimplemented, err.Error()) } @@ -72,7 +72,7 @@ func FromGRPC(err error) error { case codes.FailedPrecondition: cls = ErrFailedPrecondition case codes.Unimplemented: - cls = ErrNotSupported + cls = ErrNotImplemented default: cls = ErrUnknown } diff --git a/vendor/github.com/containerd/containerd/image.go b/vendor/github.com/containerd/containerd/image.go index bf725cba6..88f9b2e0f 100644 --- a/vendor/github.com/containerd/containerd/image.go +++ b/vendor/github.com/containerd/containerd/image.go @@ -2,10 +2,9 @@ package containerd import ( "context" - "encoding/json" - "github.com/containerd/containerd/content" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/rootfs" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -46,7 +45,7 @@ func (i *image) Target() ocispec.Descriptor { func (i *image) RootFS(ctx context.Context) ([]digest.Digest, error) { provider := i.client.ContentStore() - return i.i.RootFS(ctx, provider) + return i.i.RootFS(ctx, provider, platforms.Format(platforms.Default())) } func (i *image) Size(ctx context.Context) (int64, error) { @@ -56,11 +55,11 @@ func (i *image) Size(ctx context.Context) (int64, error) { func (i *image) Config(ctx context.Context) (ocispec.Descriptor, error) { provider := i.client.ContentStore() - return i.i.Config(ctx, provider) + return i.i.Config(ctx, provider, platforms.Format(platforms.Default())) } func (i *image) Unpack(ctx context.Context, snapshotterName string) error { - layers, err := i.getLayers(ctx) + layers, err := i.getLayers(ctx, platforms.Format(platforms.Default())) if err != nil { return err } @@ -98,19 +97,15 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string) error { return nil } -func (i *image) getLayers(ctx context.Context) ([]rootfs.Layer, error) { +func (i *image) getLayers(ctx context.Context, platform string) ([]rootfs.Layer, error) { cs := i.client.ContentStore() - // TODO: Support manifest list - p, err := content.ReadBlob(ctx, cs, i.i.Target.Digest) + manifest, err := images.Manifest(ctx, cs, i.i.Target, platform) if err != nil { - return nil, errors.Wrapf(err, "failed to read manifest blob") + return nil, errors.Wrap(err, "") } - var manifest ocispec.Manifest - if err := json.Unmarshal(p, &manifest); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal manifest") - } - diffIDs, err := i.i.RootFS(ctx, cs) + + diffIDs, err := i.i.RootFS(ctx, cs, platform) if err != nil { return nil, errors.Wrap(err, "failed to resolve rootfs") } diff --git a/vendor/github.com/containerd/containerd/images/handlers.go b/vendor/github.com/containerd/containerd/images/handlers.go index 452dd7cc5..53248f018 100644 --- a/vendor/github.com/containerd/containerd/images/handlers.go +++ b/vendor/github.com/containerd/containerd/images/handlers.go @@ -67,7 +67,7 @@ func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) err children, err := handler.Handle(ctx, desc) if err != nil { if errors.Cause(err) == SkipDesc { - return nil // don't traverse the children. + continue // don't traverse the children. } return err } diff --git a/vendor/github.com/containerd/containerd/images/image.go b/vendor/github.com/containerd/containerd/images/image.go index ce7189822..674cf0874 100644 --- a/vendor/github.com/containerd/containerd/images/image.go +++ b/vendor/github.com/containerd/containerd/images/image.go @@ -6,6 +6,8 @@ import ( "time" "github.com/containerd/containerd/content" + "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/platforms" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -39,16 +41,16 @@ type Store interface { // // The caller can then use the descriptor to resolve and process the // configuration of the image. -func (image *Image) Config(ctx context.Context, provider content.Provider) (ocispec.Descriptor, error) { - return Config(ctx, provider, image.Target) +func (image *Image) Config(ctx context.Context, provider content.Provider, platform string) (ocispec.Descriptor, error) { + return Config(ctx, provider, image.Target, platform) } // RootFS returns the unpacked diffids that make up and images rootfs. // // These are used to verify that a set of layers unpacked to the expected // values. -func (image *Image) RootFS(ctx context.Context, provider content.Provider) ([]digest.Digest, error) { - desc, err := image.Config(ctx, provider) +func (image *Image) RootFS(ctx context.Context, provider content.Provider, platform string) ([]digest.Digest, error) { + desc, err := image.Config(ctx, provider, platform) if err != nil { return nil, err } @@ -67,17 +69,23 @@ func (image *Image) Size(ctx context.Context, provider content.Provider) (int64, }), ChildrenHandler(provider)), image.Target) } -// Config resolves the image configuration descriptor using a content provided -// to resolve child resources on the image. -// -// The caller can then use the descriptor to resolve and process the -// configuration of the image. -func Config(ctx context.Context, provider content.Provider, image ocispec.Descriptor) (ocispec.Descriptor, error) { - var configDesc ocispec.Descriptor - return configDesc, Walk(ctx, HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { - switch image.MediaType { +func Manifest(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform string) (ocispec.Manifest, error) { + var ( + matcher platforms.Matcher + m *ocispec.Manifest + err error + ) + if platform != "" { + matcher, err = platforms.Parse(platform) + if err != nil { + return ocispec.Manifest{}, err + } + } + + if err := Walk(ctx, HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { + switch desc.MediaType { case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest: - p, err := content.ReadBlob(ctx, provider, image.Digest) + p, err := content.ReadBlob(ctx, provider, desc.Digest) if err != nil { return nil, err } @@ -87,14 +95,108 @@ func Config(ctx context.Context, provider content.Provider, image ocispec.Descri return nil, err } - configDesc = manifest.Config + if platform != "" { + if desc.Platform != nil && !matcher.Match(*desc.Platform) { + return nil, nil + } + + if desc.Platform == nil { + p, err := content.ReadBlob(ctx, provider, manifest.Config.Digest) + if err != nil { + return nil, err + } + + var image ocispec.Image + if err := json.Unmarshal(p, &image); err != nil { + return nil, err + } + + if !matcher.Match(platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture})) { + return nil, nil + } + + } + } + + m = &manifest return nil, nil - default: - return nil, errors.New("could not resolve config") + case MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: + p, err := content.ReadBlob(ctx, provider, desc.Digest) + if err != nil { + return nil, err + } + + var idx ocispec.Index + if err := json.Unmarshal(p, &idx); err != nil { + return nil, err + } + + if platform == "" { + return idx.Manifests, nil + } + + var descs []ocispec.Descriptor + for _, d := range idx.Manifests { + if d.Platform == nil || matcher.Match(*d.Platform) { + descs = append(descs, d) + } + } + + return descs, nil + + } + return nil, errors.New("could not resolve manifest") + }), image); err != nil { + return ocispec.Manifest{}, err + } + + if m == nil { + return ocispec.Manifest{}, errors.Wrap(errdefs.ErrNotFound, "manifest not found") + } + + return *m, nil +} + +// Config resolves the image configuration descriptor using a content provided +// to resolve child resources on the image. +// +// The caller can then use the descriptor to resolve and process the +// configuration of the image. +func Config(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform string) (ocispec.Descriptor, error) { + manifest, err := Manifest(ctx, provider, image, platform) + if err != nil { + return ocispec.Descriptor{}, err + } + return manifest.Config, err +} + +// Platforms returns one or more platforms supported by the image. +func Platforms(ctx context.Context, provider content.Provider, image ocispec.Descriptor) ([]ocispec.Platform, error) { + var platformSpecs []ocispec.Platform + return platformSpecs, Walk(ctx, Handlers(HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { + if desc.Platform != nil { + platformSpecs = append(platformSpecs, *desc.Platform) + return nil, SkipDesc } - }), image) + switch desc.MediaType { + case MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig: + p, err := content.ReadBlob(ctx, provider, desc.Digest) + if err != nil { + return nil, err + } + + var image ocispec.Image + if err := json.Unmarshal(p, &image); err != nil { + return nil, err + } + + platformSpecs = append(platformSpecs, + platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture})) + } + return nil, nil + }), ChildrenHandler(provider)), image) } // RootFS returns the unpacked diffids that make up and images rootfs. diff --git a/vendor/github.com/containerd/containerd/platforms/database.go b/vendor/github.com/containerd/containerd/platforms/database.go new file mode 100644 index 000000000..bd66e2517 --- /dev/null +++ b/vendor/github.com/containerd/containerd/platforms/database.go @@ -0,0 +1,77 @@ +package platforms + +import ( + "runtime" + "strings" +) + +// These function are generated from from https://golang.org/src/go/build/syslist.go. +// +// We use switch statements because they are slightly faster than map lookups +// and use a little less memory. + +// isKnownOS returns true if we know about the operating system. +// +// The OS value should be normalized before calling this function. +func isKnownOS(os string) bool { + switch os { + case "android", "darwin", "dragonfly", "freebsd", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos": + return true + } + return false +} + +// isKnownArch returns true if we know about the architecture. +// +// The arch value should be normalized before being passed to this function. +func isKnownArch(arch string) bool { + switch arch { + case "386", "amd64", "amd64p32", "arm", "armbe", "arm64", "arm64be", "ppc64", "ppc64le", "mips", "mipsle", "mips64", "mips64le", "mips64p32", "mips64p32le", "ppc", "s390", "s390x", "sparc", "sparc64": + return true + } + return false +} + +func normalizeOS(os string) string { + if os == "" { + return runtime.GOOS + } + os = strings.ToLower(os) + + switch os { + case "macos": + os = "darwin" + } + return os +} + +// normalizeArch normalizes the architecture. +func normalizeArch(arch, variant string) (string, string) { + arch, variant = strings.ToLower(arch), strings.ToLower(variant) + switch arch { + case "i386": + arch = "386" + variant = "" + case "x86_64", "x86-64": + arch = "amd64" + variant = "" + case "aarch64": + arch = "arm64" + variant = "" // v8 is implied + case "armhf": + arch = "arm" + variant = "" + case "armel": + arch = "arm" + variant = "v6" + case "arm": + switch variant { + case "v7", "7": + variant = "v7" + case "5", "6", "8": + variant = "v" + variant + } + } + + return arch, variant +} diff --git a/vendor/github.com/containerd/containerd/platforms/defaults.go b/vendor/github.com/containerd/containerd/platforms/defaults.go new file mode 100644 index 000000000..d49912052 --- /dev/null +++ b/vendor/github.com/containerd/containerd/platforms/defaults.go @@ -0,0 +1,16 @@ +package platforms + +import ( + "runtime" + + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +// Default returns the current platform's default platform specification. +func Default() specs.Platform { + return specs.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + // TODO(stevvooe): Need to resolve GOARM for arm hosts. + } +} diff --git a/vendor/github.com/containerd/containerd/platforms/platforms.go b/vendor/github.com/containerd/containerd/platforms/platforms.go new file mode 100644 index 000000000..56c6ddc51 --- /dev/null +++ b/vendor/github.com/containerd/containerd/platforms/platforms.go @@ -0,0 +1,236 @@ +// Package platforms provides a toolkit for normalizing, matching and +// specifying container platforms. +// +// Centered around OCI platform specifications, we define a string-based +// specifier syntax that can be used for user input. With a specifier, users +// only need to specify the parts of the platform that are relevant to their +// context, providing an operating system or architecture or both. +// +// How do I use this package? +// +// The vast majority of use cases should simply use the match function with +// user input. The first step is to parse a specifier into a matcher: +// +// m, err := Parse("linux") +// if err != nil { ... } +// +// Once you have a matcher, use it to match against the platform declared by a +// component, typically from an image or runtime. Since extracting an images +// platform is a little more involved, we'll use an example against the +// platform default: +// +// if ok := m.Match(Default()); !ok { /* doesn't match */ } +// +// This can be composed in loops for resolving runtimes or used as a filter for +// fetch and select images. +// +// More details of the specifier syntax and platform spec follow. +// +// Declaring Platform Support +// +// Components that have strict platform requirements should use the OCI +// platform specification to declare their support. Typically, this will be +// images and runtimes that should make these declaring which platform they +// support specifically. This looks roughly as follows: +// +// type Platform struct { +// Architecture string +// OS string +// Variant string +// } +// +// Most images and runtimes should at least set Architecture and OS, according +// to their GOARCH and GOOS values, respectively (follow the OCI image +// specification when in doubt). ARM should set variant under certain +// discussions, which are outlined below. +// +// Platform Specifiers +// +// While the OCI platform specifications provide a tool for components to +// specify structured information, user input typically doesn't need the full +// context and much can be inferred. To solve this problem, we introduced +// "specifiers". A specifier has the format +// `||/[/]`. The user can provide either the +// operating system or the architecture or both. +// +// An example of a common specifier is `linux/amd64`. If the host has a default +// of runtime that matches this, the user can simply provide the component that +// matters. For example, if a image provides amd64 and arm64 support, the +// operating system, `linux` can be inferred, so they only have to provide +// `arm64` or `amd64`. Similar behavior is implemented for operating systems, +// where the architecture may be known but a runtime may support images from +// different operating systems. +// +// Normalization +// +// Because not all users are familiar with the way the Go runtime represents +// platforms, several normalizations have been provided to make this package +// easier to user. +// +// The following are performed for architectures: +// +// Value Normalized +// aarch64 arm64 +// armhf arm +// armel arm/v6 +// i386 386 +// x86_64 amd64 +// x86-64 amd64 +// +// We also normalize the operating system `macos` to `darwin`. +// +// ARM Support +// +// To qualify ARM architecture, the Variant field is used to qualify the arm +// version. The most common arm version, v7, is represented without the variant +// unless it is explicitly provided. This is treated as equivalent to armhf. A +// previous architecture, armel, will be normalized to arm/v6. +// +// While these normalizations are provided, their support on arm platforms has +// not yet been fully implemented and tested. +package platforms + +import ( + "regexp" + "runtime" + "strings" + + "github.com/containerd/containerd/errdefs" + specs "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +var ( + specifierRe = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) +) + +// Matcher matches platforms specifications, provided by an image or runtime. +type Matcher interface { + Spec() specs.Platform + Match(platform specs.Platform) bool +} + +type matcher struct { + specs.Platform +} + +func (m *matcher) Spec() specs.Platform { + return m.Platform +} + +func (m *matcher) Match(platform specs.Platform) bool { + normalized := Normalize(platform) + return m.OS == normalized.OS && + m.Architecture == normalized.Architecture && + m.Variant == normalized.Variant +} + +func (m *matcher) String() string { + return Format(m.Platform) +} + +// Parse parses the platform specifier syntax into a platform declaration. +// +// Platform specifiers are in the format `||/[/]`. +// The minimum required information for a platform specifier is the operating +// system or architecture. If there is only a single string (no slashes), the +// value will be matched against the known set of operating systems, then fall +// back to the known set of architectures. The missing component will be +// inferred based on the local environment. +// +// Applications should opt to use `Match` over directly parsing specifiers. +func Parse(specifier string) (Matcher, error) { + if strings.Contains(specifier, "*") { + // TODO(stevvooe): need to work out exact wildcard handling + return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: wildcards not yet supported", specifier) + } + + parts := strings.Split(specifier, "/") + + for _, part := range parts { + if !specifierRe.MatchString(part) { + return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%q is an invalid component of %q: platform specifier component must match %q", part, specifier, specifierRe.String()) + } + } + + var p specs.Platform + switch len(parts) { + case 1: + // in this case, we will test that the value might be an OS, then look + // it up. If it is not known, we'll treat it as an architecture. Since + // we have very little information about the platform here, we are + // going to be a little more strict if we don't know about the argument + // value. + p.OS = normalizeOS(parts[0]) + if isKnownOS(p.OS) { + // picks a default architecture + p.Architecture = runtime.GOARCH + if p.Architecture == "arm" { + // TODO(stevvooe): Resolve arm variant, if not v6 (default) + return nil, errors.Wrapf(errdefs.ErrNotImplemented, "arm support not fully implemented") + } + + return &matcher{p}, nil + } + + p.Architecture, p.Variant = normalizeArch(parts[0], "") + if isKnownArch(p.Architecture) { + p.OS = runtime.GOOS + return &matcher{p}, nil + } + + return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: unknown operating system or architecture", specifier) + case 2: + // In this case, we treat as a regular os/arch pair. We don't care + // about whether or not we know of the platform. + p.OS = normalizeOS(parts[0]) + p.Architecture, p.Variant = normalizeArch(parts[1], "") + + return &matcher{p}, nil + case 3: + // we have a fully specified variant, this is rare + p.OS = normalizeOS(parts[0]) + p.Architecture, p.Variant = normalizeArch(parts[1], parts[2]) + + return &matcher{p}, nil + } + + return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: cannot parse platform specifier", specifier) +} + +// Format returns a string specifier from the provided platform specification. +func Format(platform specs.Platform) string { + if platform.OS == "" { + return "unknown" + } + + return joinNotEmpty(platform.OS, platform.Architecture, platform.Variant) +} + +func joinNotEmpty(s ...string) string { + var ss []string + for _, s := range s { + if s == "" { + continue + } + + ss = append(ss, s) + } + + return strings.Join(ss, "/") +} + +// Normalize validates and translate the platform to the canonical value. +// +// For example, if "Aarch64" is encountered, we change it to "arm64" or if +// "x86_64" is encountered, it becomes "amd64". +func Normalize(platform specs.Platform) specs.Platform { + platform.OS = normalizeOS(platform.OS) + platform.Architecture, platform.Variant = normalizeArch(platform.Architecture, platform.Variant) + + // these fields are deprecated, remove them + platform.OSFeatures = nil + platform.OSVersion = "" + + return platform +} diff --git a/vendor/github.com/containerd/containerd/services/diff/service.go b/vendor/github.com/containerd/containerd/services/diff/service.go index 6d65fd951..af6326b60 100644 --- a/vendor/github.com/containerd/containerd/services/diff/service.go +++ b/vendor/github.com/containerd/containerd/services/diff/service.go @@ -74,7 +74,7 @@ func (s *service) Apply(ctx context.Context, er *diffapi.ApplyRequest) (*diffapi for _, differ := range s.differs { ocidesc, err = differ.Apply(ctx, desc, mounts) - if !errdefs.IsNotSupported(err) { + if !errdefs.IsNotImplemented(err) { break } } @@ -99,7 +99,7 @@ func (s *service) Diff(ctx context.Context, dr *diffapi.DiffRequest) (*diffapi.D for _, differ := range s.differs { ocidesc, err = differ.DiffMounts(ctx, aMounts, bMounts, dr.MediaType, dr.Ref) - if !errdefs.IsNotSupported(err) { + if !errdefs.IsNotImplemented(err) { break } } diff --git a/vendor/github.com/containerd/containerd/spec_opts_unix.go b/vendor/github.com/containerd/containerd/spec_opts_unix.go index ed2f038f0..b529cfb87 100644 --- a/vendor/github.com/containerd/containerd/spec_opts_unix.go +++ b/vendor/github.com/containerd/containerd/spec_opts_unix.go @@ -19,6 +19,7 @@ import ( "github.com/containerd/containerd/fs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/platforms" "github.com/opencontainers/image-spec/identity" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runc/libcontainer/user" @@ -72,7 +73,7 @@ func WithImageConfig(i Image) SpecOpts { image = i.(*image) store = client.ContentStore() ) - ic, err := image.i.Config(ctx, store) + ic, err := image.i.Config(ctx, store, platforms.Format(platforms.Default())) if err != nil { return err } @@ -235,7 +236,7 @@ func WithRemappedSnapshotView(id string, i Image, uid, gid uint32) NewContainerO func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool) NewContainerOpts { return func(ctx context.Context, client *Client, c *containers.Container) error { - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore()) + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Format(platforms.Default())) if err != nil { return err } diff --git a/vendor/github.com/containerd/containerd/spec_opts_windows.go b/vendor/github.com/containerd/containerd/spec_opts_windows.go index 33ee74e3f..33aba1a9c 100644 --- a/vendor/github.com/containerd/containerd/spec_opts_windows.go +++ b/vendor/github.com/containerd/containerd/spec_opts_windows.go @@ -10,6 +10,7 @@ import ( "github.com/containerd/containerd/containers" "github.com/containerd/containerd/content" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/runtime-spec/specs-go" ) @@ -20,7 +21,7 @@ func WithImageConfig(i Image) SpecOpts { image = i.(*image) store = client.ContentStore() ) - ic, err := image.i.Config(ctx, store) + ic, err := image.i.Config(ctx, store, platforms.Format(platforms.Default())) if err != nil { return err } diff --git a/vendor/github.com/containerd/containerd/spec_unix.go b/vendor/github.com/containerd/containerd/spec_unix.go index a3b44c53b..4d431f420 100644 --- a/vendor/github.com/containerd/containerd/spec_unix.go +++ b/vendor/github.com/containerd/containerd/spec_unix.go @@ -25,7 +25,7 @@ var ( } ) -func defaltCaps() []string { +func defaultCaps() []string { return []string{ "CAP_CHOWN", "CAP_DAC_OVERRIDE", @@ -79,10 +79,10 @@ func createDefaultSpec() (*specs.Spec, error) { GID: 0, }, Capabilities: &specs.LinuxCapabilities{ - Bounding: defaltCaps(), - Permitted: defaltCaps(), - Inheritable: defaltCaps(), - Effective: defaltCaps(), + Bounding: defaultCaps(), + Permitted: defaultCaps(), + Inheritable: defaultCaps(), + Effective: defaultCaps(), }, Rlimits: []specs.POSIXRlimit{ { diff --git a/vendor/github.com/containerd/containerd/vendor.conf b/vendor/github.com/containerd/containerd/vendor.conf index bdad8f1f4..1698fbe70 100644 --- a/vendor/github.com/containerd/containerd/vendor.conf +++ b/vendor/github.com/containerd/containerd/vendor.conf @@ -15,7 +15,7 @@ github.com/docker/go-units v0.3.1 github.com/gogo/protobuf d2e1ade2d719b78fe5b061b4c18a9f7111b5bdc8 github.com/golang/protobuf 5a0f697c9ed9d68fef0116532c6e05cfeae00e55 github.com/opencontainers/runtime-spec v1.0.0 -github.com/opencontainers/runc e775f0fba3ea329b8b766451c892c41a3d49594d +github.com/opencontainers/runc 593914b8bd5448a93f7c3e4902a03408b6d5c0ce github.com/sirupsen/logrus v1.0.0 github.com/containerd/btrfs cc52c4dea2ce11a44e6639e561bb5c2af9ada9e3 github.com/stretchr/testify v1.1.4 diff --git a/vendor/github.com/hashicorp/errwrap/LICENSE b/vendor/github.com/hashicorp/errwrap/LICENSE new file mode 100644 index 000000000..c33dcc7c9 --- /dev/null +++ b/vendor/github.com/hashicorp/errwrap/LICENSE @@ -0,0 +1,354 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. + diff --git a/vendor/github.com/hashicorp/errwrap/README.md b/vendor/github.com/hashicorp/errwrap/README.md new file mode 100644 index 000000000..1c95f5978 --- /dev/null +++ b/vendor/github.com/hashicorp/errwrap/README.md @@ -0,0 +1,89 @@ +# errwrap + +`errwrap` is a package for Go that formalizes the pattern of wrapping errors +and checking if an error contains another error. + +There is a common pattern in Go of taking a returned `error` value and +then wrapping it (such as with `fmt.Errorf`) before returning it. The problem +with this pattern is that you completely lose the original `error` structure. + +Arguably the _correct_ approach is that you should make a custom structure +implementing the `error` interface, and have the original error as a field +on that structure, such [as this example](http://golang.org/pkg/os/#PathError). +This is a good approach, but you have to know the entire chain of possible +rewrapping that happens, when you might just care about one. + +`errwrap` formalizes this pattern (it doesn't matter what approach you use +above) by giving a single interface for wrapping errors, checking if a specific +error is wrapped, and extracting that error. + +## Installation and Docs + +Install using `go get github.com/hashicorp/errwrap`. + +Full documentation is available at +http://godoc.org/github.com/hashicorp/errwrap + +## Usage + +#### Basic Usage + +Below is a very basic example of its usage: + +```go +// A function that always returns an error, but wraps it, like a real +// function might. +func tryOpen() error { + _, err := os.Open("/i/dont/exist") + if err != nil { + return errwrap.Wrapf("Doesn't exist: {{err}}", err) + } + + return nil +} + +func main() { + err := tryOpen() + + // We can use the Contains helpers to check if an error contains + // another error. It is safe to do this with a nil error, or with + // an error that doesn't even use the errwrap package. + if errwrap.Contains(err, ErrNotExist) { + // Do something + } + if errwrap.ContainsType(err, new(os.PathError)) { + // Do something + } + + // Or we can use the associated `Get` functions to just extract + // a specific error. This would return nil if that specific error doesn't + // exist. + perr := errwrap.GetType(err, new(os.PathError)) +} +``` + +#### Custom Types + +If you're already making custom types that properly wrap errors, then +you can get all the functionality of `errwraps.Contains` and such by +implementing the `Wrapper` interface with just one function. Example: + +```go +type AppError { + Code ErrorCode + Err error +} + +func (e *AppError) WrappedErrors() []error { + return []error{e.Err} +} +``` + +Now this works: + +```go +err := &AppError{Err: fmt.Errorf("an error")} +if errwrap.ContainsType(err, fmt.Errorf("")) { + // This will work! +} +``` diff --git a/vendor/github.com/hashicorp/errwrap/errwrap.go b/vendor/github.com/hashicorp/errwrap/errwrap.go new file mode 100644 index 000000000..a733bef18 --- /dev/null +++ b/vendor/github.com/hashicorp/errwrap/errwrap.go @@ -0,0 +1,169 @@ +// Package errwrap implements methods to formalize error wrapping in Go. +// +// All of the top-level functions that take an `error` are built to be able +// to take any error, not just wrapped errors. This allows you to use errwrap +// without having to type-check and type-cast everywhere. +package errwrap + +import ( + "errors" + "reflect" + "strings" +) + +// WalkFunc is the callback called for Walk. +type WalkFunc func(error) + +// Wrapper is an interface that can be implemented by custom types to +// have all the Contains, Get, etc. functions in errwrap work. +// +// When Walk reaches a Wrapper, it will call the callback for every +// wrapped error in addition to the wrapper itself. Since all the top-level +// functions in errwrap use Walk, this means that all those functions work +// with your custom type. +type Wrapper interface { + WrappedErrors() []error +} + +// Wrap defines that outer wraps inner, returning an error type that +// can be cleanly used with the other methods in this package, such as +// Contains, GetAll, etc. +// +// This function won't modify the error message at all (the outer message +// will be used). +func Wrap(outer, inner error) error { + return &wrappedError{ + Outer: outer, + Inner: inner, + } +} + +// Wrapf wraps an error with a formatting message. This is similar to using +// `fmt.Errorf` to wrap an error. If you're using `fmt.Errorf` to wrap +// errors, you should replace it with this. +// +// format is the format of the error message. The string '{{err}}' will +// be replaced with the original error message. +func Wrapf(format string, err error) error { + outerMsg := "" + if err != nil { + outerMsg = err.Error() + } + + outer := errors.New(strings.Replace( + format, "{{err}}", outerMsg, -1)) + + return Wrap(outer, err) +} + +// Contains checks if the given error contains an error with the +// message msg. If err is not a wrapped error, this will always return +// false unless the error itself happens to match this msg. +func Contains(err error, msg string) bool { + return len(GetAll(err, msg)) > 0 +} + +// ContainsType checks if the given error contains an error with +// the same concrete type as v. If err is not a wrapped error, this will +// check the err itself. +func ContainsType(err error, v interface{}) bool { + return len(GetAllType(err, v)) > 0 +} + +// Get is the same as GetAll but returns the deepest matching error. +func Get(err error, msg string) error { + es := GetAll(err, msg) + if len(es) > 0 { + return es[len(es)-1] + } + + return nil +} + +// GetType is the same as GetAllType but returns the deepest matching error. +func GetType(err error, v interface{}) error { + es := GetAllType(err, v) + if len(es) > 0 { + return es[len(es)-1] + } + + return nil +} + +// GetAll gets all the errors that might be wrapped in err with the +// given message. The order of the errors is such that the outermost +// matching error (the most recent wrap) is index zero, and so on. +func GetAll(err error, msg string) []error { + var result []error + + Walk(err, func(err error) { + if err.Error() == msg { + result = append(result, err) + } + }) + + return result +} + +// GetAllType gets all the errors that are the same type as v. +// +// The order of the return value is the same as described in GetAll. +func GetAllType(err error, v interface{}) []error { + var result []error + + var search string + if v != nil { + search = reflect.TypeOf(v).String() + } + Walk(err, func(err error) { + var needle string + if err != nil { + needle = reflect.TypeOf(err).String() + } + + if needle == search { + result = append(result, err) + } + }) + + return result +} + +// Walk walks all the wrapped errors in err and calls the callback. If +// err isn't a wrapped error, this will be called once for err. If err +// is a wrapped error, the callback will be called for both the wrapper +// that implements error as well as the wrapped error itself. +func Walk(err error, cb WalkFunc) { + if err == nil { + return + } + + switch e := err.(type) { + case *wrappedError: + cb(e.Outer) + Walk(e.Inner, cb) + case Wrapper: + cb(err) + + for _, err := range e.WrappedErrors() { + Walk(err, cb) + } + default: + cb(err) + } +} + +// wrappedError is an implementation of error that has both the +// outer and inner errors. +type wrappedError struct { + Outer error + Inner error +} + +func (w *wrappedError) Error() string { + return w.Outer.Error() +} + +func (w *wrappedError) WrappedErrors() []error { + return []error{w.Outer, w.Inner} +} diff --git a/vendor/github.com/hashicorp/go-multierror/LICENSE b/vendor/github.com/hashicorp/go-multierror/LICENSE new file mode 100644 index 000000000..82b4de97c --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/LICENSE @@ -0,0 +1,353 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/go-multierror/README.md b/vendor/github.com/hashicorp/go-multierror/README.md new file mode 100644 index 000000000..ead5830f7 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/README.md @@ -0,0 +1,97 @@ +# go-multierror + +[![Build Status](http://img.shields.io/travis/hashicorp/go-multierror.svg?style=flat-square)][travis] +[![Go Documentation](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godocs] + +[travis]: https://travis-ci.org/hashicorp/go-multierror +[godocs]: https://godoc.org/github.com/hashicorp/go-multierror + +`go-multierror` is a package for Go that provides a mechanism for +representing a list of `error` values as a single `error`. + +This allows a function in Go to return an `error` that might actually +be a list of errors. If the caller knows this, they can unwrap the +list and access the errors. If the caller doesn't know, the error +formats to a nice human-readable format. + +`go-multierror` implements the +[errwrap](https://github.com/hashicorp/errwrap) interface so that it can +be used with that library, as well. + +## Installation and Docs + +Install using `go get github.com/hashicorp/go-multierror`. + +Full documentation is available at +http://godoc.org/github.com/hashicorp/go-multierror + +## Usage + +go-multierror is easy to use and purposely built to be unobtrusive in +existing Go applications/libraries that may not be aware of it. + +**Building a list of errors** + +The `Append` function is used to create a list of errors. This function +behaves a lot like the Go built-in `append` function: it doesn't matter +if the first argument is nil, a `multierror.Error`, or any other `error`, +the function behaves as you would expect. + +```go +var result error + +if err := step1(); err != nil { + result = multierror.Append(result, err) +} +if err := step2(); err != nil { + result = multierror.Append(result, err) +} + +return result +``` + +**Customizing the formatting of the errors** + +By specifying a custom `ErrorFormat`, you can customize the format +of the `Error() string` function: + +```go +var result *multierror.Error + +// ... accumulate errors here, maybe using Append + +if result != nil { + result.ErrorFormat = func([]error) string { + return "errors!" + } +} +``` + +**Accessing the list of errors** + +`multierror.Error` implements `error` so if the caller doesn't know about +multierror, it will work just fine. But if you're aware a multierror might +be returned, you can use type switches to access the list of errors: + +```go +if err := something(); err != nil { + if merr, ok := err.(*multierror.Error); ok { + // Use merr.Errors + } +} +``` + +**Returning a multierror only if there are errors** + +If you build a `multierror.Error`, you can use the `ErrorOrNil` function +to return an `error` implementation only if there are errors to return: + +```go +var result *multierror.Error + +// ... accumulate errors here + +// Return the `error` only if errors were added to the multierror, otherwise +// return nil since there are no errors. +return result.ErrorOrNil() +``` diff --git a/vendor/github.com/hashicorp/go-multierror/append.go b/vendor/github.com/hashicorp/go-multierror/append.go new file mode 100644 index 000000000..775b6e753 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/append.go @@ -0,0 +1,41 @@ +package multierror + +// Append is a helper function that will append more errors +// onto an Error in order to create a larger multi-error. +// +// If err is not a multierror.Error, then it will be turned into +// one. If any of the errs are multierr.Error, they will be flattened +// one level into err. +func Append(err error, errs ...error) *Error { + switch err := err.(type) { + case *Error: + // Typed nils can reach here, so initialize if we are nil + if err == nil { + err = new(Error) + } + + // Go through each error and flatten + for _, e := range errs { + switch e := e.(type) { + case *Error: + if e != nil { + err.Errors = append(err.Errors, e.Errors...) + } + default: + if e != nil { + err.Errors = append(err.Errors, e) + } + } + } + + return err + default: + newErrs := make([]error, 0, len(errs)+1) + if err != nil { + newErrs = append(newErrs, err) + } + newErrs = append(newErrs, errs...) + + return Append(&Error{}, newErrs...) + } +} diff --git a/vendor/github.com/hashicorp/go-multierror/flatten.go b/vendor/github.com/hashicorp/go-multierror/flatten.go new file mode 100644 index 000000000..aab8e9abe --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/flatten.go @@ -0,0 +1,26 @@ +package multierror + +// Flatten flattens the given error, merging any *Errors together into +// a single *Error. +func Flatten(err error) error { + // If it isn't an *Error, just return the error as-is + if _, ok := err.(*Error); !ok { + return err + } + + // Otherwise, make the result and flatten away! + flatErr := new(Error) + flatten(err, flatErr) + return flatErr +} + +func flatten(err error, flatErr *Error) { + switch err := err.(type) { + case *Error: + for _, e := range err.Errors { + flatten(e, flatErr) + } + default: + flatErr.Errors = append(flatErr.Errors, err) + } +} diff --git a/vendor/github.com/hashicorp/go-multierror/format.go b/vendor/github.com/hashicorp/go-multierror/format.go new file mode 100644 index 000000000..6c7a3cc91 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/format.go @@ -0,0 +1,27 @@ +package multierror + +import ( + "fmt" + "strings" +) + +// ErrorFormatFunc is a function callback that is called by Error to +// turn the list of errors into a string. +type ErrorFormatFunc func([]error) string + +// ListFormatFunc is a basic formatter that outputs the number of errors +// that occurred along with a bullet point list of the errors. +func ListFormatFunc(es []error) string { + if len(es) == 1 { + return fmt.Sprintf("1 error occurred:\n\n* %s", es[0]) + } + + points := make([]string, len(es)) + for i, err := range es { + points[i] = fmt.Sprintf("* %s", err) + } + + return fmt.Sprintf( + "%d errors occurred:\n\n%s", + len(es), strings.Join(points, "\n")) +} diff --git a/vendor/github.com/hashicorp/go-multierror/multierror.go b/vendor/github.com/hashicorp/go-multierror/multierror.go new file mode 100644 index 000000000..2ea082732 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/multierror.go @@ -0,0 +1,51 @@ +package multierror + +import ( + "fmt" +) + +// Error is an error type to track multiple errors. This is used to +// accumulate errors in cases and return them as a single "error". +type Error struct { + Errors []error + ErrorFormat ErrorFormatFunc +} + +func (e *Error) Error() string { + fn := e.ErrorFormat + if fn == nil { + fn = ListFormatFunc + } + + return fn(e.Errors) +} + +// ErrorOrNil returns an error interface if this Error represents +// a list of errors, or returns nil if the list of errors is empty. This +// function is useful at the end of accumulation to make sure that the value +// returned represents the existence of errors. +func (e *Error) ErrorOrNil() error { + if e == nil { + return nil + } + if len(e.Errors) == 0 { + return nil + } + + return e +} + +func (e *Error) GoString() string { + return fmt.Sprintf("*%#v", *e) +} + +// WrappedErrors returns the list of errors that this Error is wrapping. +// It is an implementatin of the errwrap.Wrapper interface so that +// multierror.Error can be used with that library. +// +// This method is not safe to be called concurrently and is no different +// than accessing the Errors field directly. It is implementd only to +// satisfy the errwrap.Wrapper interface. +func (e *Error) WrappedErrors() []error { + return e.Errors +} diff --git a/vendor/github.com/hashicorp/go-multierror/prefix.go b/vendor/github.com/hashicorp/go-multierror/prefix.go new file mode 100644 index 000000000..5c477abe4 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/prefix.go @@ -0,0 +1,37 @@ +package multierror + +import ( + "fmt" + + "github.com/hashicorp/errwrap" +) + +// Prefix is a helper function that will prefix some text +// to the given error. If the error is a multierror.Error, then +// it will be prefixed to each wrapped error. +// +// This is useful to use when appending multiple multierrors +// together in order to give better scoping. +func Prefix(err error, prefix string) error { + if err == nil { + return nil + } + + format := fmt.Sprintf("%s {{err}}", prefix) + switch err := err.(type) { + case *Error: + // Typed nils can reach here, so initialize if we are nil + if err == nil { + err = new(Error) + } + + // Wrap each of the errors + for i, e := range err.Errors { + err.Errors[i] = errwrap.Wrapf(format, e) + } + + return err + default: + return errwrap.Wrapf(format, err) + } +} diff --git a/vendor/github.com/opencontainers/runtime-tools/error/error.go b/vendor/github.com/opencontainers/runtime-tools/error/error.go new file mode 100644 index 000000000..f5a90800e --- /dev/null +++ b/vendor/github.com/opencontainers/runtime-tools/error/error.go @@ -0,0 +1,92 @@ +// Package error implements generic tooling for tracking RFC 2119 +// violations and linking back to the appropriate specification section. +package error + +import ( + "fmt" + "strings" +) + +// Level represents the RFC 2119 compliance levels +type Level int + +const ( + // MAY-level + + // May represents 'MAY' in RFC 2119. + May Level = iota + // Optional represents 'OPTIONAL' in RFC 2119. + Optional + + // SHOULD-level + + // Should represents 'SHOULD' in RFC 2119. + Should + // ShouldNot represents 'SHOULD NOT' in RFC 2119. + ShouldNot + // Recommended represents 'RECOMMENDED' in RFC 2119. + Recommended + // NotRecommended represents 'NOT RECOMMENDED' in RFC 2119. + NotRecommended + + // MUST-level + + // Must represents 'MUST' in RFC 2119 + Must + // MustNot represents 'MUST NOT' in RFC 2119. + MustNot + // Shall represents 'SHALL' in RFC 2119. + Shall + // ShallNot represents 'SHALL NOT' in RFC 2119. + ShallNot + // Required represents 'REQUIRED' in RFC 2119. + Required +) + +// Error represents an error with compliance level and specification reference. +type Error struct { + // Level represents the RFC 2119 compliance level. + Level Level + + // Reference is a URL for the violated specification requirement. + Reference string + + // Err holds additional details about the violation. + Err error +} + +// ParseLevel takes a string level and returns the RFC 2119 compliance level constant. +func ParseLevel(level string) (Level, error) { + switch strings.ToUpper(level) { + case "MAY": + fallthrough + case "OPTIONAL": + return May, nil + case "SHOULD": + fallthrough + case "SHOULDNOT": + fallthrough + case "RECOMMENDED": + fallthrough + case "NOTRECOMMENDED": + return Should, nil + case "MUST": + fallthrough + case "MUSTNOT": + fallthrough + case "SHALL": + fallthrough + case "SHALLNOT": + fallthrough + case "REQUIRED": + return Must, nil + } + + var l Level + return l, fmt.Errorf("%q is not a valid compliance level", level) +} + +// Error returns the error message with specification reference. +func (err *Error) Error() string { + return fmt.Sprintf("%s\nRefer to: %s", err.Err.Error(), err.Reference) +} diff --git a/vendor/github.com/opencontainers/runtime-tools/generate/generate.go b/vendor/github.com/opencontainers/runtime-tools/generate/generate.go index ed80742be..84762c3cb 100644 --- a/vendor/github.com/opencontainers/runtime-tools/generate/generate.go +++ b/vendor/github.com/opencontainers/runtime-tools/generate/generate.go @@ -341,6 +341,13 @@ func (g *Generator) RemoveAnnotation(key string) { delete(g.spec.Annotations, key) } +// SetProcessConsoleSize sets g.spec.Process.ConsoleSize. +func (g *Generator) SetProcessConsoleSize(width, height uint) { + g.initSpecProcessConsoleSize() + g.spec.Process.ConsoleSize.Width = width + g.spec.Process.ConsoleSize.Height = height +} + // SetProcessUID sets g.spec.Process.User.UID. func (g *Generator) SetProcessUID(uid uint32) { g.initSpecProcess() @@ -737,6 +744,38 @@ func (g *Generator) ClearPreStartHooks() { func (g *Generator) AddPreStartHook(path string, args []string) { g.initSpecHooks() hook := rspec.Hook{Path: path, Args: args} + for i, hook := range g.spec.Hooks.Prestart { + if hook.Path == path { + g.spec.Hooks.Prestart[i] = hook + return + } + } + g.spec.Hooks.Prestart = append(g.spec.Hooks.Prestart, hook) +} + +// AddPreStartHookEnv adds envs of a prestart hook into g.spec.Hooks.Prestart. +func (g *Generator) AddPreStartHookEnv(path string, envs []string) { + g.initSpecHooks() + for i, hook := range g.spec.Hooks.Prestart { + if hook.Path == path { + g.spec.Hooks.Prestart[i].Env = envs + return + } + } + hook := rspec.Hook{Path: path, Env: envs} + g.spec.Hooks.Prestart = append(g.spec.Hooks.Prestart, hook) +} + +// AddPreStartHookTimeout adds timeout of a prestart hook into g.spec.Hooks.Prestart. +func (g *Generator) AddPreStartHookTimeout(path string, timeout int) { + g.initSpecHooks() + for i, hook := range g.spec.Hooks.Prestart { + if hook.Path == path { + g.spec.Hooks.Prestart[i].Timeout = &timeout + return + } + } + hook := rspec.Hook{Path: path, Timeout: &timeout} g.spec.Hooks.Prestart = append(g.spec.Hooks.Prestart, hook) } @@ -755,6 +794,38 @@ func (g *Generator) ClearPostStopHooks() { func (g *Generator) AddPostStopHook(path string, args []string) { g.initSpecHooks() hook := rspec.Hook{Path: path, Args: args} + for i, hook := range g.spec.Hooks.Poststop { + if hook.Path == path { + g.spec.Hooks.Poststop[i] = hook + return + } + } + g.spec.Hooks.Poststop = append(g.spec.Hooks.Poststop, hook) +} + +// AddPostStopHookEnv adds envs of a poststop hook into g.spec.Hooks.Poststop. +func (g *Generator) AddPostStopHookEnv(path string, envs []string) { + g.initSpecHooks() + for i, hook := range g.spec.Hooks.Poststop { + if hook.Path == path { + g.spec.Hooks.Poststop[i].Env = envs + return + } + } + hook := rspec.Hook{Path: path, Env: envs} + g.spec.Hooks.Poststop = append(g.spec.Hooks.Poststop, hook) +} + +// AddPostStopHookTimeout adds timeout of a poststop hook into g.spec.Hooks.Poststop. +func (g *Generator) AddPostStopHookTimeout(path string, timeout int) { + g.initSpecHooks() + for i, hook := range g.spec.Hooks.Poststop { + if hook.Path == path { + g.spec.Hooks.Poststop[i].Timeout = &timeout + return + } + } + hook := rspec.Hook{Path: path, Timeout: &timeout} g.spec.Hooks.Poststop = append(g.spec.Hooks.Poststop, hook) } @@ -773,6 +844,38 @@ func (g *Generator) ClearPostStartHooks() { func (g *Generator) AddPostStartHook(path string, args []string) { g.initSpecHooks() hook := rspec.Hook{Path: path, Args: args} + for i, hook := range g.spec.Hooks.Poststart { + if hook.Path == path { + g.spec.Hooks.Poststart[i] = hook + return + } + } + g.spec.Hooks.Poststart = append(g.spec.Hooks.Poststart, hook) +} + +// AddPostStartHookEnv adds envs of a poststart hook into g.spec.Hooks.Poststart. +func (g *Generator) AddPostStartHookEnv(path string, envs []string) { + g.initSpecHooks() + for i, hook := range g.spec.Hooks.Poststart { + if hook.Path == path { + g.spec.Hooks.Poststart[i].Env = envs + return + } + } + hook := rspec.Hook{Path: path, Env: envs} + g.spec.Hooks.Poststart = append(g.spec.Hooks.Poststart, hook) +} + +// AddPostStartHookTimeout adds timeout of a poststart hook into g.spec.Hooks.Poststart. +func (g *Generator) AddPostStartHookTimeout(path string, timeout int) { + g.initSpecHooks() + for i, hook := range g.spec.Hooks.Poststart { + if hook.Path == path { + g.spec.Hooks.Poststart[i].Timeout = &timeout + return + } + } + hook := rspec.Hook{Path: path, Timeout: &timeout} g.spec.Hooks.Poststart = append(g.spec.Hooks.Poststart, hook) } @@ -853,11 +956,12 @@ func (g *Generator) SetupPrivileged(privileged bool) { } g.initSpecLinux() g.initSpecProcessCapabilities() - g.spec.Process.Capabilities.Bounding = finalCapList - g.spec.Process.Capabilities.Effective = finalCapList - g.spec.Process.Capabilities.Inheritable = finalCapList - g.spec.Process.Capabilities.Permitted = finalCapList - g.spec.Process.Capabilities.Ambient = finalCapList + g.ClearProcessCapabilities() + g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding, finalCapList...) + g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective, finalCapList...) + g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable, finalCapList...) + g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted, finalCapList...) + g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient, finalCapList...) g.spec.Process.SelinuxLabel = "" g.spec.Process.ApparmorProfile = "" g.spec.Linux.Seccomp = nil @@ -885,40 +989,60 @@ func (g *Generator) AddProcessCapability(c string) error { g.initSpecProcessCapabilities() + var foundBounding bool for _, cap := range g.spec.Process.Capabilities.Bounding { if strings.ToUpper(cap) == cp { - return nil + foundBounding = true + break } } - g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding, cp) + if !foundBounding { + g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding, cp) + } + var foundEffective bool for _, cap := range g.spec.Process.Capabilities.Effective { if strings.ToUpper(cap) == cp { - return nil + foundEffective = true + break } } - g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective, cp) + if !foundEffective { + g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective, cp) + } + var foundInheritable bool for _, cap := range g.spec.Process.Capabilities.Inheritable { if strings.ToUpper(cap) == cp { - return nil + foundInheritable = true + break } } - g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable, cp) + if !foundInheritable { + g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable, cp) + } + var foundPermitted bool for _, cap := range g.spec.Process.Capabilities.Permitted { if strings.ToUpper(cap) == cp { - return nil + foundPermitted = true + break } } - g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted, cp) + if !foundPermitted { + g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted, cp) + } + var foundAmbient bool for _, cap := range g.spec.Process.Capabilities.Ambient { if strings.ToUpper(cap) == cp { - return nil + foundAmbient = true + break } } - g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient, cp) + if !foundAmbient { + g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient, cp) + } return nil } @@ -926,42 +1050,49 @@ func (g *Generator) AddProcessCapability(c string) error { // DropProcessCapability drops a process capability from g.spec.Process.Capabilities. func (g *Generator) DropProcessCapability(c string) error { cp := strings.ToUpper(c) - if err := validate.CapValid(cp, g.HostSpecific); err != nil { - return err - } g.initSpecProcessCapabilities() + // we don't care about order...and this is way faster... + removeFunc := func(s []string, i int) []string { + s[i] = s[len(s)-1] + return s[:len(s)-1] + } + for i, cap := range g.spec.Process.Capabilities.Bounding { if strings.ToUpper(cap) == cp { - g.spec.Process.Capabilities.Bounding = append(g.spec.Process.Capabilities.Bounding[:i], g.spec.Process.Capabilities.Bounding[i+1:]...) + g.spec.Process.Capabilities.Bounding = removeFunc(g.spec.Process.Capabilities.Bounding, i) } } for i, cap := range g.spec.Process.Capabilities.Effective { if strings.ToUpper(cap) == cp { - g.spec.Process.Capabilities.Effective = append(g.spec.Process.Capabilities.Effective[:i], g.spec.Process.Capabilities.Effective[i+1:]...) + g.spec.Process.Capabilities.Effective = removeFunc(g.spec.Process.Capabilities.Effective, i) } } for i, cap := range g.spec.Process.Capabilities.Inheritable { if strings.ToUpper(cap) == cp { - g.spec.Process.Capabilities.Inheritable = append(g.spec.Process.Capabilities.Inheritable[:i], g.spec.Process.Capabilities.Inheritable[i+1:]...) + g.spec.Process.Capabilities.Inheritable = removeFunc(g.spec.Process.Capabilities.Inheritable, i) } } for i, cap := range g.spec.Process.Capabilities.Permitted { if strings.ToUpper(cap) == cp { - g.spec.Process.Capabilities.Permitted = append(g.spec.Process.Capabilities.Permitted[:i], g.spec.Process.Capabilities.Permitted[i+1:]...) + g.spec.Process.Capabilities.Permitted = removeFunc(g.spec.Process.Capabilities.Permitted, i) } } for i, cap := range g.spec.Process.Capabilities.Ambient { if strings.ToUpper(cap) == cp { - g.spec.Process.Capabilities.Ambient = append(g.spec.Process.Capabilities.Ambient[:i], g.spec.Process.Capabilities.Ambient[i+1:]...) + g.spec.Process.Capabilities.Ambient = removeFunc(g.spec.Process.Capabilities.Ambient, i) } } + if err := validate.CapValid(cp, false); err != nil { + return err + } + return nil } diff --git a/vendor/github.com/opencontainers/runtime-tools/generate/spec.go b/vendor/github.com/opencontainers/runtime-tools/generate/spec.go index 523066c05..519d25448 100644 --- a/vendor/github.com/opencontainers/runtime-tools/generate/spec.go +++ b/vendor/github.com/opencontainers/runtime-tools/generate/spec.go @@ -17,6 +17,13 @@ func (g *Generator) initSpecProcess() { } } +func (g *Generator) initSpecProcessConsoleSize() { + g.initSpecProcess() + if g.spec.Process.ConsoleSize == nil { + g.spec.Process.ConsoleSize = &rspec.Box{} + } +} + func (g *Generator) initSpecProcessCapabilities() { g.initSpecProcess() if g.spec.Process.Capabilities == nil { diff --git a/vendor/github.com/opencontainers/runtime-tools/specerror/error.go b/vendor/github.com/opencontainers/runtime-tools/specerror/error.go new file mode 100644 index 000000000..c75bb6b14 --- /dev/null +++ b/vendor/github.com/opencontainers/runtime-tools/specerror/error.go @@ -0,0 +1,170 @@ +// Package specerror implements runtime-spec-specific tooling for +// tracking RFC 2119 violations. +package specerror + +import ( + "fmt" + + "github.com/hashicorp/go-multierror" + rfc2119 "github.com/opencontainers/runtime-tools/error" +) + +const referenceTemplate = "https://github.com/opencontainers/runtime-spec/blob/v%s/%s" + +// Code represents the spec violation, enumerating both +// configuration violations and runtime violations. +type Code int + +const ( + // NonError represents that an input is not an error + NonError Code = iota + // NonRFCError represents that an error is not a rfc2119 error + NonRFCError + + // ConfigFileExistence represents the error code of 'config.json' existence test + ConfigFileExistence + // ArtifactsInSingleDir represents the error code of artifacts place test + ArtifactsInSingleDir + + // SpecVersion represents the error code of specfication version test + SpecVersion + + // RootOnNonHyperV represents the error code of root setting test on non hyper-v containers + RootOnNonHyperV + // RootOnHyperV represents the error code of root setting test on hyper-v containers + RootOnHyperV + // PathFormatOnWindows represents the error code of the path format test on Window + PathFormatOnWindows + // PathName represents the error code of the path name test + PathName + // PathExistence represents the error code of the path existence test + PathExistence + // ReadonlyFilesystem represents the error code of readonly test + ReadonlyFilesystem + // ReadonlyOnWindows represents the error code of readonly setting test on Windows + ReadonlyOnWindows + + // DefaultFilesystems represents the error code of default filesystems test + DefaultFilesystems + + // CreateWithID represents the error code of 'create' lifecyle test with 'id' provided + CreateWithID + // CreateWithUniqueID represents the error code of 'create' lifecyle test with unique 'id' provided + CreateWithUniqueID + // CreateNewContainer represents the error code 'create' lifecyle test that creates new container + CreateNewContainer +) + +type errorTemplate struct { + Level rfc2119.Level + Reference func(version string) (reference string, err error) +} + +// Error represents a runtime-spec violation. +type Error struct { + // Err holds the RFC 2119 violation. + Err rfc2119.Error + + // Code is a matchable holds a Code + Code Code +} + +var ( + containerFormatRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "bundle.md#container-format"), nil + } + specVersionRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#specification-version"), nil + } + rootRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config.md#root"), nil + } + defaultFSRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "config-linux.md#default-filesystems"), nil + } + runtimeCreateRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "runtime.md#create"), nil + } +) + +var ociErrors = map[Code]errorTemplate{ + // Bundle.md + // Container Format + ConfigFileExistence: {Level: rfc2119.Must, Reference: containerFormatRef}, + ArtifactsInSingleDir: {Level: rfc2119.Must, Reference: containerFormatRef}, + + // Config.md + // Specification Version + SpecVersion: {Level: rfc2119.Must, Reference: specVersionRef}, + // Root + RootOnNonHyperV: {Level: rfc2119.Required, Reference: rootRef}, + RootOnHyperV: {Level: rfc2119.Must, Reference: rootRef}, + // TODO: add tests for 'PathFormatOnWindows' + PathFormatOnWindows: {Level: rfc2119.Must, Reference: rootRef}, + PathName: {Level: rfc2119.Should, Reference: rootRef}, + PathExistence: {Level: rfc2119.Must, Reference: rootRef}, + ReadonlyFilesystem: {Level: rfc2119.Must, Reference: rootRef}, + ReadonlyOnWindows: {Level: rfc2119.Must, Reference: rootRef}, + + // Config-Linux.md + // Default Filesystems + DefaultFilesystems: {Level: rfc2119.Should, Reference: defaultFSRef}, + + // Runtime.md + // Create + CreateWithID: {Level: rfc2119.Must, Reference: runtimeCreateRef}, + CreateWithUniqueID: {Level: rfc2119.Must, Reference: runtimeCreateRef}, + CreateNewContainer: {Level: rfc2119.Must, Reference: runtimeCreateRef}, +} + +// Error returns the error message with specification reference. +func (err *Error) Error() string { + return err.Err.Error() +} + +// NewError creates an Error referencing a spec violation. The error +// can be cast to an *Error for extracting structured information +// about the level of the violation and a reference to the violated +// spec condition. +// +// A version string (for the version of the spec that was violated) +// must be set to get a working URL. +func NewError(code Code, err error, version string) error { + template := ociErrors[code] + reference, err2 := template.Reference(version) + if err2 != nil { + return err2 + } + return &Error{ + Err: rfc2119.Error{ + Level: template.Level, + Reference: reference, + Err: err, + }, + Code: code, + } +} + +// FindError finds an error from a source error (multiple error) and +// returns the error code if found. +// If the source error is nil or empty, return NonError. +// If the source error is not a multiple error, return NonRFCError. +func FindError(err error, code Code) Code { + if err == nil { + return NonError + } + + if merr, ok := err.(*multierror.Error); ok { + if merr.ErrorOrNil() == nil { + return NonError + } + for _, e := range merr.Errors { + if rfcErr, ok := e.(*Error); ok { + if rfcErr.Code == code { + return code + } + } + } + } + return NonRFCError +} diff --git a/vendor/github.com/opencontainers/runtime-tools/validate/error.go b/vendor/github.com/opencontainers/runtime-tools/validate/error.go deleted file mode 100644 index 754d032b5..000000000 --- a/vendor/github.com/opencontainers/runtime-tools/validate/error.go +++ /dev/null @@ -1,110 +0,0 @@ -package validate - -import ( - "errors" - "fmt" - "strings" - - rspec "github.com/opencontainers/runtime-spec/specs-go" -) - -// ComplianceLevel represents the OCI compliance levels -type ComplianceLevel int - -const ( - // MAY-level - - // ComplianceMay represents 'MAY' in RFC2119 - ComplianceMay ComplianceLevel = iota - // ComplianceOptional represents 'OPTIONAL' in RFC2119 - ComplianceOptional - - // SHOULD-level - - // ComplianceShould represents 'SHOULD' in RFC2119 - ComplianceShould - // ComplianceShouldNot represents 'SHOULD NOT' in RFC2119 - ComplianceShouldNot - // ComplianceRecommended represents 'RECOMMENDED' in RFC2119 - ComplianceRecommended - // ComplianceNotRecommended represents 'NOT RECOMMENDED' in RFC2119 - ComplianceNotRecommended - - // MUST-level - - // ComplianceMust represents 'MUST' in RFC2119 - ComplianceMust - // ComplianceMustNot represents 'MUST NOT' in RFC2119 - ComplianceMustNot - // ComplianceShall represents 'SHALL' in RFC2119 - ComplianceShall - // ComplianceShallNot represents 'SHALL NOT' in RFC2119 - ComplianceShallNot - // ComplianceRequired represents 'REQUIRED' in RFC2119 - ComplianceRequired -) - -// ErrorCode represents the compliance content -type ErrorCode int - -const ( - // DefaultFilesystems represents the error code of default filesystems test - DefaultFilesystems ErrorCode = iota -) - -// Error represents an error with compliance level and OCI reference -type Error struct { - Level ComplianceLevel - Reference string - Err error -} - -const referencePrefix = "https://github.com/opencontainers/runtime-spec/blob" - -var ociErrors = map[ErrorCode]Error{ - DefaultFilesystems: Error{Level: ComplianceShould, Reference: "config-linux.md#default-filesystems"}, -} - -// ParseLevel takes a string level and returns the OCI compliance level constant -func ParseLevel(level string) (ComplianceLevel, error) { - switch strings.ToUpper(level) { - case "MAY": - fallthrough - case "OPTIONAL": - return ComplianceMay, nil - case "SHOULD": - fallthrough - case "SHOULDNOT": - fallthrough - case "RECOMMENDED": - fallthrough - case "NOTRECOMMENDED": - return ComplianceShould, nil - case "MUST": - fallthrough - case "MUSTNOT": - fallthrough - case "SHALL": - fallthrough - case "SHALLNOT": - fallthrough - case "REQUIRED": - return ComplianceMust, nil - } - - var l ComplianceLevel - return l, fmt.Errorf("%q is not a valid compliance level", level) -} - -// NewError creates an Error by ErrorCode and message -func NewError(code ErrorCode, msg string) error { - err := ociErrors[code] - err.Err = errors.New(msg) - - return &err -} - -// Error returns the error message with OCI reference -func (oci *Error) Error() string { - return fmt.Sprintf("%s\nRefer to: %s/v%s/%s", oci.Err.Error(), referencePrefix, rspec.Version, oci.Reference) -} diff --git a/vendor/github.com/opencontainers/runtime-tools/validate/validate.go b/vendor/github.com/opencontainers/runtime-tools/validate/validate.go index db54d2d9c..6e26bc13a 100644 --- a/vendor/github.com/opencontainers/runtime-tools/validate/validate.go +++ b/vendor/github.com/opencontainers/runtime-tools/validate/validate.go @@ -3,21 +3,27 @@ package validate import ( "bufio" "encoding/json" + "errors" "fmt" "io/ioutil" "net" "os" "path/filepath" "reflect" + "regexp" "runtime" "strings" + "syscall" "unicode" "unicode/utf8" "github.com/blang/semver" + "github.com/hashicorp/go-multierror" rspec "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" "github.com/syndtr/gocapability/capability" + + "github.com/opencontainers/runtime-tools/specerror" ) const specConfig = "config.json" @@ -70,7 +76,7 @@ func NewValidatorFromPath(bundlePath string, hostSpecific bool, platform string) platform = runtime.GOOS } if bundlePath == "" { - return Validator{}, fmt.Errorf("Bundle path shouldn't be empty") + return Validator{}, fmt.Errorf("bundle path shouldn't be empty") } if _, err := os.Stat(bundlePath); err != nil { @@ -80,7 +86,7 @@ func NewValidatorFromPath(bundlePath string, hostSpecific bool, platform string) configPath := filepath.Join(bundlePath, specConfig) content, err := ioutil.ReadFile(configPath) if err != nil { - return Validator{}, err + return Validator{}, specerror.NewError(specerror.ConfigFileExistence, err, rspec.Version) } if !utf8.Valid(content) { return Validator{}, fmt.Errorf("%q is not encoded in UTF-8", configPath) @@ -94,28 +100,46 @@ func NewValidatorFromPath(bundlePath string, hostSpecific bool, platform string) } // CheckAll checks all parts of runtime bundle -func (v *Validator) CheckAll() (msgs []string) { - msgs = append(msgs, v.CheckPlatform()...) - msgs = append(msgs, v.CheckRootfsPath()...) - msgs = append(msgs, v.CheckMandatoryFields()...) - msgs = append(msgs, v.CheckSemVer()...) - msgs = append(msgs, v.CheckMounts()...) - msgs = append(msgs, v.CheckProcess()...) - msgs = append(msgs, v.CheckHooks()...) - if v.spec.Linux != nil { - msgs = append(msgs, v.CheckLinux()...) - } +func (v *Validator) CheckAll() error { + var errs *multierror.Error + errs = multierror.Append(errs, v.CheckPlatform()) + errs = multierror.Append(errs, v.CheckRoot()) + errs = multierror.Append(errs, v.CheckMandatoryFields()) + errs = multierror.Append(errs, v.CheckSemVer()) + errs = multierror.Append(errs, v.CheckMounts()) + errs = multierror.Append(errs, v.CheckProcess()) + errs = multierror.Append(errs, v.CheckHooks()) + errs = multierror.Append(errs, v.CheckLinux()) - return + return errs.ErrorOrNil() } -// CheckRootfsPath checks status of v.spec.Root.Path -func (v *Validator) CheckRootfsPath() (msgs []string) { - logrus.Debugf("check rootfs path") +// CheckRoot checks status of v.spec.Root +func (v *Validator) CheckRoot() (errs error) { + logrus.Debugf("check root") + + if v.platform == "windows" && v.spec.Windows != nil && v.spec.Windows.HyperV != nil { + if v.spec.Root != nil { + errs = multierror.Append(errs, + specerror.NewError(specerror.RootOnHyperV, fmt.Errorf("for Hyper-V containers, Root must not be set"), rspec.Version)) + return + } + return + } else if v.spec.Root == nil { + errs = multierror.Append(errs, + specerror.NewError(specerror.RootOnNonHyperV, fmt.Errorf("for non-Hyper-V containers, Root must be set"), rspec.Version)) + return + } absBundlePath, err := filepath.Abs(v.bundlePath) if err != nil { - msgs = append(msgs, fmt.Sprintf("unable to convert %q to an absolute path", v.bundlePath)) + errs = multierror.Append(errs, fmt.Errorf("unable to convert %q to an absolute path", v.bundlePath)) + return + } + + if filepath.Base(v.spec.Root.Path) != "rootfs" { + errs = multierror.Append(errs, + specerror.NewError(specerror.PathName, fmt.Errorf("path name should be the conventional 'rootfs'"), rspec.Version)) } var rootfsPath string @@ -128,24 +152,29 @@ func (v *Validator) CheckRootfsPath() (msgs []string) { rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path) absRootPath, err = filepath.Abs(rootfsPath) if err != nil { - msgs = append(msgs, fmt.Sprintf("unable to convert %q to an absolute path", rootfsPath)) + errs = multierror.Append(errs, fmt.Errorf("unable to convert %q to an absolute path", rootfsPath)) + return } } if fi, err := os.Stat(rootfsPath); err != nil { - msgs = append(msgs, fmt.Sprintf("Cannot find the root path %q", rootfsPath)) + errs = multierror.Append(errs, + specerror.NewError(specerror.PathExistence, fmt.Errorf("cannot find the root path %q", rootfsPath), rspec.Version)) } else if !fi.IsDir() { - msgs = append(msgs, fmt.Sprintf("The root path %q is not a directory.", rootfsPath)) + errs = multierror.Append(errs, + specerror.NewError(specerror.PathExistence, fmt.Errorf("root.path %q is not a directory", rootfsPath), rspec.Version)) } rootParent := filepath.Dir(absRootPath) if absRootPath == string(filepath.Separator) || rootParent != absBundlePath { - msgs = append(msgs, fmt.Sprintf("root.path is %q, but it MUST be a child of %q", v.spec.Root.Path, absBundlePath)) + errs = multierror.Append(errs, + specerror.NewError(specerror.ArtifactsInSingleDir, fmt.Errorf("root.path is %q, but it MUST be a child of %q", v.spec.Root.Path, absBundlePath), rspec.Version)) } if v.platform == "windows" { if v.spec.Root.Readonly { - msgs = append(msgs, "root.readonly field MUST be omitted or false when target platform is windows") + errs = multierror.Append(errs, + specerror.NewError(specerror.ReadonlyOnWindows, fmt.Errorf("root.readonly field MUST be omitted or false when target platform is windows"), rspec.Version)) } } @@ -153,53 +182,54 @@ func (v *Validator) CheckRootfsPath() (msgs []string) { } // CheckSemVer checks v.spec.Version -func (v *Validator) CheckSemVer() (msgs []string) { +func (v *Validator) CheckSemVer() (errs error) { logrus.Debugf("check semver") version := v.spec.Version _, err := semver.Parse(version) if err != nil { - msgs = append(msgs, fmt.Sprintf("%q is not valid SemVer: %s", version, err.Error())) + errs = multierror.Append(errs, + specerror.NewError(specerror.SpecVersion, fmt.Errorf("%q is not valid SemVer: %s", version, err.Error()), rspec.Version)) } if version != rspec.Version { - msgs = append(msgs, fmt.Sprintf("internal error: validate currently only handles version %s, but the supplied configuration targets %s", rspec.Version, version)) + errs = multierror.Append(errs, fmt.Errorf("validate currently only handles version %s, but the supplied configuration targets %s", rspec.Version, version)) } return } // CheckHooks check v.spec.Hooks -func (v *Validator) CheckHooks() (msgs []string) { +func (v *Validator) CheckHooks() (errs error) { logrus.Debugf("check hooks") if v.spec.Hooks != nil { - msgs = append(msgs, checkEventHooks("pre-start", v.spec.Hooks.Prestart, v.HostSpecific)...) - msgs = append(msgs, checkEventHooks("post-start", v.spec.Hooks.Poststart, v.HostSpecific)...) - msgs = append(msgs, checkEventHooks("post-stop", v.spec.Hooks.Poststop, v.HostSpecific)...) + errs = multierror.Append(errs, checkEventHooks("pre-start", v.spec.Hooks.Prestart, v.HostSpecific)) + errs = multierror.Append(errs, checkEventHooks("post-start", v.spec.Hooks.Poststart, v.HostSpecific)) + errs = multierror.Append(errs, checkEventHooks("post-stop", v.spec.Hooks.Poststop, v.HostSpecific)) } return } -func checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (msgs []string) { +func checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (errs error) { for _, hook := range hooks { if !filepath.IsAbs(hook.Path) { - msgs = append(msgs, fmt.Sprintf("The %s hook %v: is not absolute path", hookType, hook.Path)) + errs = multierror.Append(errs, fmt.Errorf("the %s hook %v: is not absolute path", hookType, hook.Path)) } if hostSpecific { fi, err := os.Stat(hook.Path) if err != nil { - msgs = append(msgs, fmt.Sprintf("Cannot find %s hook: %v", hookType, hook.Path)) + errs = multierror.Append(errs, fmt.Errorf("cannot find %s hook: %v", hookType, hook.Path)) } if fi.Mode()&0111 == 0 { - msgs = append(msgs, fmt.Sprintf("The %s hook %v: is not executable", hookType, hook.Path)) + errs = multierror.Append(errs, fmt.Errorf("the %s hook %v: is not executable", hookType, hook.Path)) } } for _, env := range hook.Env { if !envValid(env) { - msgs = append(msgs, fmt.Sprintf("Env %q for hook %v is in the invalid form.", env, hook.Path)) + errs = multierror.Append(errs, fmt.Errorf("env %q for hook %v is in the invalid form", env, hook.Path)) } } } @@ -208,22 +238,26 @@ func checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (ms } // CheckProcess checks v.spec.Process -func (v *Validator) CheckProcess() (msgs []string) { +func (v *Validator) CheckProcess() (errs error) { logrus.Debugf("check process") + if v.spec.Process == nil { + return + } + process := v.spec.Process if !filepath.IsAbs(process.Cwd) { - msgs = append(msgs, fmt.Sprintf("cwd %q is not an absolute path", process.Cwd)) + errs = multierror.Append(errs, fmt.Errorf("cwd %q is not an absolute path", process.Cwd)) } for _, env := range process.Env { if !envValid(env) { - msgs = append(msgs, fmt.Sprintf("env %q should be in the form of 'key=value'. The left hand side must consist solely of letters, digits, and underscores '_'.", env)) + errs = multierror.Append(errs, fmt.Errorf("env %q should be in the form of 'key=value'. The left hand side must consist solely of letters, digits, and underscores '_'", env)) } } if len(process.Args) == 0 { - msgs = append(msgs, fmt.Sprintf("args must not be empty")) + errs = multierror.Append(errs, fmt.Errorf("args must not be empty")) } else { if filepath.IsAbs(process.Args[0]) { var rootfsPath string @@ -237,27 +271,27 @@ func (v *Validator) CheckProcess() (msgs []string) { if os.IsNotExist(err) { logrus.Warnf("executable %q is not available in rootfs currently", process.Args[0]) } else if err != nil { - msgs = append(msgs, err.Error()) + errs = multierror.Append(errs, err) } else { m := fileinfo.Mode() if m.IsDir() || m&0111 == 0 { - msgs = append(msgs, fmt.Sprintf("arg %q is not executable", process.Args[0])) + errs = multierror.Append(errs, fmt.Errorf("arg %q is not executable", process.Args[0])) } } } } if v.spec.Process.Capabilities != nil { - msgs = append(msgs, v.CheckCapabilities()...) + errs = multierror.Append(errs, v.CheckCapabilities()) } - msgs = append(msgs, v.CheckRlimits()...) + errs = multierror.Append(errs, v.CheckRlimits()) if v.platform == "linux" { if len(process.ApparmorProfile) > 0 { profilePath := filepath.Join(v.bundlePath, v.spec.Root.Path, "/etc/apparmor.d", process.ApparmorProfile) _, err := os.Stat(profilePath) if err != nil { - msgs = append(msgs, err.Error()) + errs = multierror.Append(errs, err) } } } @@ -266,7 +300,7 @@ func (v *Validator) CheckProcess() (msgs []string) { } // CheckCapabilities checks v.spec.Process.Capabilities -func (v *Validator) CheckCapabilities() (msgs []string) { +func (v *Validator) CheckCapabilities() (errs error) { process := v.spec.Process if v.platform == "linux" { var effective, permitted, inheritable, ambient bool @@ -290,29 +324,33 @@ func (v *Validator) CheckCapabilities() (msgs []string) { for capability, owns := range caps { if err := CapValid(capability, v.HostSpecific); err != nil { - msgs = append(msgs, fmt.Sprintf("capability %q is not valid, man capabilities(7)", capability)) + errs = multierror.Append(errs, fmt.Errorf("capability %q is not valid, man capabilities(7)", capability)) } effective, permitted, ambient, inheritable = false, false, false, false for _, set := range owns { if set == "effective" { effective = true + continue } if set == "inheritable" { inheritable = true + continue } if set == "permitted" { permitted = true + continue } if set == "ambient" { ambient = true + continue } } if effective && !permitted { - msgs = append(msgs, fmt.Sprintf("effective capability %q is not allowed, as it's not permitted", capability)) + errs = multierror.Append(errs, fmt.Errorf("effective capability %q is not allowed, as it's not permitted", capability)) } if ambient && !(effective && inheritable) { - msgs = append(msgs, fmt.Sprintf("ambient capability %q is not allowed, as it's not permitted and inheribate", capability)) + errs = multierror.Append(errs, fmt.Errorf("ambient capability %q is not allowed, as it's not permitted and inheribate", capability)) } } } else { @@ -323,15 +361,15 @@ func (v *Validator) CheckCapabilities() (msgs []string) { } // CheckRlimits checks v.spec.Process.Rlimits -func (v *Validator) CheckRlimits() (msgs []string) { +func (v *Validator) CheckRlimits() (errs error) { process := v.spec.Process for index, rlimit := range process.Rlimits { for i := index + 1; i < len(process.Rlimits); i++ { if process.Rlimits[index].Type == process.Rlimits[i].Type { - msgs = append(msgs, fmt.Sprintf("rlimit can not contain the same type %q.", process.Rlimits[index].Type)) + errs = multierror.Append(errs, fmt.Errorf("rlimit can not contain the same type %q", process.Rlimits[index].Type)) } } - msgs = append(msgs, v.rlimitValid(rlimit)...) + errs = multierror.Append(errs, v.rlimitValid(rlimit)) } return @@ -379,24 +417,49 @@ func supportedMountTypes(OS string, hostSpecific bool) (map[string]bool, error) } // CheckMounts checks v.spec.Mounts -func (v *Validator) CheckMounts() (msgs []string) { +func (v *Validator) CheckMounts() (errs error) { logrus.Debugf("check mounts") supportedTypes, err := supportedMountTypes(v.platform, v.HostSpecific) if err != nil { - msgs = append(msgs, err.Error()) + errs = multierror.Append(errs, err) return } - for _, mount := range v.spec.Mounts { - if supportedTypes != nil { - if !supportedTypes[mount.Type] { - msgs = append(msgs, fmt.Sprintf("Unsupported mount type %q", mount.Type)) + for i, mountA := range v.spec.Mounts { + if supportedTypes != nil && !supportedTypes[mountA.Type] { + errs = multierror.Append(errs, fmt.Errorf("unsupported mount type %q", mountA.Type)) + } + if v.platform == "windows" { + if err := pathValid(v.platform, mountA.Destination); err != nil { + errs = multierror.Append(errs, err) + } + if err := pathValid(v.platform, mountA.Source); err != nil { + errs = multierror.Append(errs, err) + } + } else { + if err := pathValid(v.platform, mountA.Destination); err != nil { + errs = multierror.Append(errs, err) } } - - if !filepath.IsAbs(mount.Destination) { - msgs = append(msgs, fmt.Sprintf("destination %v is not an absolute path", mount.Destination)) + for j, mountB := range v.spec.Mounts { + if i == j { + continue + } + // whether B.Desination is nested within A.Destination + nested, err := nestedValid(v.platform, mountA.Destination, mountB.Destination) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + if nested { + if v.platform == "windows" && i < j { + errs = multierror.Append(errs, fmt.Errorf("on Windows, %v nested within %v is forbidden", mountB.Destination, mountA.Destination)) + } + if i > j { + logrus.Warnf("%v will be covered by %v", mountB.Destination, mountA.Destination) + } + } } } @@ -404,17 +467,17 @@ func (v *Validator) CheckMounts() (msgs []string) { } // CheckPlatform checks v.platform -func (v *Validator) CheckPlatform() (msgs []string) { +func (v *Validator) CheckPlatform() (errs error) { logrus.Debugf("check platform") if v.platform != "linux" && v.platform != "solaris" && v.platform != "windows" { - msgs = append(msgs, fmt.Sprintf("platform %q is not supported", v.platform)) + errs = multierror.Append(errs, fmt.Errorf("platform %q is not supported", v.platform)) return } if v.platform == "windows" { if v.spec.Windows == nil { - msgs = append(msgs, "'windows' MUST be set when platform is `windows`") + errs = multierror.Append(errs, errors.New("'windows' MUST be set when platform is `windows`")) } } @@ -422,10 +485,14 @@ func (v *Validator) CheckPlatform() (msgs []string) { } // CheckLinux checks v.spec.Linux -func (v *Validator) CheckLinux() (msgs []string) { +func (v *Validator) CheckLinux() (errs error) { logrus.Debugf("check linux") - var typeList = map[rspec.LinuxNamespaceType]struct { + if v.spec.Linux == nil { + return + } + + var nsTypeList = map[rspec.LinuxNamespaceType]struct { num int newExist bool }{ @@ -441,58 +508,142 @@ func (v *Validator) CheckLinux() (msgs []string) { for index := 0; index < len(v.spec.Linux.Namespaces); index++ { ns := v.spec.Linux.Namespaces[index] if !namespaceValid(ns) { - msgs = append(msgs, fmt.Sprintf("namespace %v is invalid.", ns)) + errs = multierror.Append(errs, fmt.Errorf("namespace %v is invalid", ns)) } - tmpItem := typeList[ns.Type] + tmpItem := nsTypeList[ns.Type] tmpItem.num = tmpItem.num + 1 if tmpItem.num > 1 { - msgs = append(msgs, fmt.Sprintf("duplicated namespace %q", ns.Type)) + errs = multierror.Append(errs, fmt.Errorf("duplicated namespace %q", ns.Type)) } if len(ns.Path) == 0 { tmpItem.newExist = true } - typeList[ns.Type] = tmpItem + nsTypeList[ns.Type] = tmpItem } - if (len(v.spec.Linux.UIDMappings) > 0 || len(v.spec.Linux.GIDMappings) > 0) && !typeList[rspec.UserNamespace].newExist { - msgs = append(msgs, "UID/GID mappings requires a new User namespace to be specified as well") + if (len(v.spec.Linux.UIDMappings) > 0 || len(v.spec.Linux.GIDMappings) > 0) && !nsTypeList[rspec.UserNamespace].newExist { + errs = multierror.Append(errs, errors.New("the UID/GID mappings requires a new User namespace to be specified as well")) } else if len(v.spec.Linux.UIDMappings) > 5 { - msgs = append(msgs, "Only 5 UID mappings are allowed (linux kernel restriction).") + errs = multierror.Append(errs, errors.New("only 5 UID mappings are allowed (linux kernel restriction)")) } else if len(v.spec.Linux.GIDMappings) > 5 { - msgs = append(msgs, "Only 5 GID mappings are allowed (linux kernel restriction).") + errs = multierror.Append(errs, errors.New("only 5 GID mappings are allowed (linux kernel restriction)")) } for k := range v.spec.Linux.Sysctl { - if strings.HasPrefix(k, "net.") && !typeList[rspec.NetworkNamespace].newExist { - msgs = append(msgs, fmt.Sprintf("Sysctl %v requires a new Network namespace to be specified as well", k)) + if strings.HasPrefix(k, "net.") && !nsTypeList[rspec.NetworkNamespace].newExist { + errs = multierror.Append(errs, fmt.Errorf("sysctl %v requires a new Network namespace to be specified as well", k)) } if strings.HasPrefix(k, "fs.mqueue.") { - if !typeList[rspec.MountNamespace].newExist || !typeList[rspec.IPCNamespace].newExist { - msgs = append(msgs, fmt.Sprintf("Sysctl %v requires a new IPC namespace and Mount namespace to be specified as well", k)) + if !nsTypeList[rspec.MountNamespace].newExist || !nsTypeList[rspec.IPCNamespace].newExist { + errs = multierror.Append(errs, fmt.Errorf("sysctl %v requires a new IPC namespace and Mount namespace to be specified as well", k)) } } } - if v.platform == "linux" && !typeList[rspec.UTSNamespace].newExist && v.spec.Hostname != "" { - msgs = append(msgs, fmt.Sprintf("On Linux, hostname requires a new UTS namespace to be specified as well")) + if v.platform == "linux" && !nsTypeList[rspec.UTSNamespace].newExist && v.spec.Hostname != "" { + errs = multierror.Append(errs, fmt.Errorf("on Linux, hostname requires a new UTS namespace to be specified as well")) } + // Linux devices validation + devList := make(map[string]bool) + devTypeList := make(map[string]bool) for index := 0; index < len(v.spec.Linux.Devices); index++ { - if !deviceValid(v.spec.Linux.Devices[index]) { - msgs = append(msgs, fmt.Sprintf("device %v is invalid.", v.spec.Linux.Devices[index])) + device := v.spec.Linux.Devices[index] + if !deviceValid(device) { + errs = multierror.Append(errs, fmt.Errorf("device %v is invalid", device)) + } + + if _, exists := devList[device.Path]; exists { + errs = multierror.Append(errs, fmt.Errorf("device %s is duplicated", device.Path)) + } else { + var rootfsPath string + if filepath.IsAbs(v.spec.Root.Path) { + rootfsPath = v.spec.Root.Path + } else { + rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path) + } + absPath := filepath.Join(rootfsPath, device.Path) + fi, err := os.Stat(absPath) + if os.IsNotExist(err) { + devList[device.Path] = true + } else if err != nil { + errs = multierror.Append(errs, err) + } else { + fStat, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + errs = multierror.Append(errs, fmt.Errorf("cannot determine state for device %s", device.Path)) + continue + } + var devType string + switch fStat.Mode & syscall.S_IFMT { + case syscall.S_IFCHR: + devType = "c" + case syscall.S_IFBLK: + devType = "b" + case syscall.S_IFIFO: + devType = "p" + default: + devType = "unmatched" + } + if devType != device.Type || (devType == "c" && device.Type == "u") { + errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path)) + continue + } + if devType != "p" { + dev := fStat.Rdev + major := (dev >> 8) & 0xfff + minor := (dev & 0xff) | ((dev >> 12) & 0xfff00) + if int64(major) != device.Major || int64(minor) != device.Minor { + errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path)) + continue + } + } + if device.FileMode != nil { + expectedPerm := *device.FileMode & os.ModePerm + actualPerm := fi.Mode() & os.ModePerm + if expectedPerm != actualPerm { + errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path)) + continue + } + } + if device.UID != nil { + if *device.UID != fStat.Uid { + errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path)) + continue + } + } + if device.GID != nil { + if *device.GID != fStat.Gid { + errs = multierror.Append(errs, fmt.Errorf("unmatched %s already exists in filesystem", device.Path)) + continue + } + } + } + } + + // unify u->c when comparing, they are synonyms + var devID string + if device.Type == "u" { + devID = fmt.Sprintf("%s:%d:%d", "c", device.Major, device.Minor) + } else { + devID = fmt.Sprintf("%s:%d:%d", device.Type, device.Major, device.Minor) + } + + if _, exists := devTypeList[devID]; exists { + logrus.Warnf("type:%s, major:%d and minor:%d for linux devices is duplicated", device.Type, device.Major, device.Minor) + } else { + devTypeList[devID] = true } } if v.spec.Linux.Resources != nil { - ms := v.CheckLinuxResources() - msgs = append(msgs, ms...) + errs = multierror.Append(errs, v.CheckLinuxResources()) } if v.spec.Linux.Seccomp != nil { - ms := v.CheckSeccomp() - msgs = append(msgs, ms...) + errs = multierror.Append(errs, v.CheckSeccomp()) } switch v.spec.Linux.RootfsPropagation { @@ -506,18 +657,18 @@ func (v *Validator) CheckLinux() (msgs []string) { case "unbindable": case "runbindable": default: - msgs = append(msgs, "rootfsPropagation must be empty or one of \"private|rprivate|slave|rslave|shared|rshared|unbindable|runbindable\"") + errs = multierror.Append(errs, errors.New("rootfsPropagation must be empty or one of \"private|rprivate|slave|rslave|shared|rshared|unbindable|runbindable\"")) } for _, maskedPath := range v.spec.Linux.MaskedPaths { if !strings.HasPrefix(maskedPath, "/") { - msgs = append(msgs, fmt.Sprintf("maskedPath %v is not an absolute path", maskedPath)) + errs = multierror.Append(errs, fmt.Errorf("maskedPath %v is not an absolute path", maskedPath)) } } for _, readonlyPath := range v.spec.Linux.ReadonlyPaths { if !strings.HasPrefix(readonlyPath, "/") { - msgs = append(msgs, fmt.Sprintf("readonlyPath %v is not an absolute path", readonlyPath)) + errs = multierror.Append(errs, fmt.Errorf("readonlyPath %v is not an absolute path", readonlyPath)) } } @@ -525,23 +676,23 @@ func (v *Validator) CheckLinux() (msgs []string) { } // CheckLinuxResources checks v.spec.Linux.Resources -func (v *Validator) CheckLinuxResources() (msgs []string) { +func (v *Validator) CheckLinuxResources() (errs error) { logrus.Debugf("check linux resources") r := v.spec.Linux.Resources if r.Memory != nil { if r.Memory.Limit != nil && r.Memory.Swap != nil && uint64(*r.Memory.Limit) > uint64(*r.Memory.Swap) { - msgs = append(msgs, fmt.Sprintf("Minimum memoryswap should be larger than memory limit")) + errs = multierror.Append(errs, fmt.Errorf("minimum memoryswap should be larger than memory limit")) } if r.Memory.Limit != nil && r.Memory.Reservation != nil && uint64(*r.Memory.Reservation) > uint64(*r.Memory.Limit) { - msgs = append(msgs, fmt.Sprintf("Minimum memory limit should be larger than memory reservation")) + errs = multierror.Append(errs, fmt.Errorf("minimum memory limit should be larger than memory reservation")) } } if r.Network != nil && v.HostSpecific { var exist bool interfaces, err := net.Interfaces() if err != nil { - msgs = append(msgs, err.Error()) + errs = multierror.Append(errs, err) return } for _, prio := range r.Network.Priorities { @@ -553,7 +704,24 @@ func (v *Validator) CheckLinuxResources() (msgs []string) { } } if !exist { - msgs = append(msgs, fmt.Sprintf("Interface %s does not exist currently", prio.Name)) + errs = multierror.Append(errs, fmt.Errorf("interface %s does not exist currently", prio.Name)) + } + } + } + for index := 0; index < len(r.Devices); index++ { + switch r.Devices[index].Type { + case "a", "b", "c": + default: + errs = multierror.Append(errs, fmt.Errorf("type of devices %s is invalid", r.Devices[index].Type)) + } + + access := []byte(r.Devices[index].Access) + for i := 0; i < len(access); i++ { + switch access[i] { + case 'r', 'w', 'm': + default: + errs = multierror.Append(errs, fmt.Errorf("access %s is invalid", r.Devices[index].Access)) + return } } } @@ -562,16 +730,16 @@ func (v *Validator) CheckLinuxResources() (msgs []string) { } // CheckSeccomp checkc v.spec.Linux.Seccomp -func (v *Validator) CheckSeccomp() (msgs []string) { +func (v *Validator) CheckSeccomp() (errs error) { logrus.Debugf("check linux seccomp") s := v.spec.Linux.Seccomp if !seccompActionValid(s.DefaultAction) { - msgs = append(msgs, fmt.Sprintf("seccomp defaultAction %q is invalid.", s.DefaultAction)) + errs = multierror.Append(errs, fmt.Errorf("seccomp defaultAction %q is invalid", s.DefaultAction)) } for index := 0; index < len(s.Syscalls); index++ { if !syscallValid(s.Syscalls[index]) { - msgs = append(msgs, fmt.Sprintf("syscall %v is invalid.", s.Syscalls[index])) + errs = multierror.Append(errs, fmt.Errorf("syscall %v is invalid", s.Syscalls[index])) } } for index := 0; index < len(s.Architectures); index++ { @@ -595,7 +763,7 @@ func (v *Validator) CheckSeccomp() (msgs []string) { case rspec.ArchPARISC: case rspec.ArchPARISC64: default: - msgs = append(msgs, fmt.Sprintf("seccomp architecture %q is invalid", s.Architectures[index])) + errs = multierror.Append(errs, fmt.Errorf("seccomp architecture %q is invalid", s.Architectures[index])) } } @@ -612,7 +780,7 @@ func CapValid(c string, hostSpecific bool) error { for _, cap := range capability.List() { if c == fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String())) { if hostSpecific && cap > LastCap() { - return fmt.Errorf("CAP_%s is not supported on the current host", c) + return fmt.Errorf("%s is not supported on the current host", c) } isValid = true break @@ -620,7 +788,7 @@ func CapValid(c string, hostSpecific bool) error { } if !isValid { - return fmt.Errorf("Invalid capability: %s", c) + return fmt.Errorf("invalid capability: %s", c) } return nil } @@ -652,9 +820,9 @@ func envValid(env string) bool { return true } -func (v *Validator) rlimitValid(rlimit rspec.POSIXRlimit) (msgs []string) { +func (v *Validator) rlimitValid(rlimit rspec.POSIXRlimit) (errs error) { if rlimit.Hard < rlimit.Soft { - msgs = append(msgs, fmt.Sprintf("hard limit of rlimit %s should not be less than soft limit", rlimit.Type)) + errs = multierror.Append(errs, fmt.Errorf("hard limit of rlimit %s should not be less than soft limit", rlimit.Type)) } if v.platform == "linux" { @@ -663,7 +831,7 @@ func (v *Validator) rlimitValid(rlimit rspec.POSIXRlimit) (msgs []string) { return } } - msgs = append(msgs, fmt.Sprintf("rlimit type %q is invalid", rlimit.Type)) + errs = multierror.Append(errs, fmt.Errorf("rlimit type %q is invalid", rlimit.Type)) } else { logrus.Warnf("process.rlimits validation not yet implemented for platform %q", v.platform) } @@ -691,6 +859,65 @@ func namespaceValid(ns rspec.LinuxNamespace) bool { return true } +func pathValid(os, path string) error { + if os == "windows" { + matched, err := regexp.MatchString("^[a-zA-Z]:(\\\\[^\\\\/<>|:*?\"]+)+$", path) + if err != nil { + return err + } + if !matched { + return fmt.Errorf("invalid windows path %v", path) + } + return nil + } + if !filepath.IsAbs(path) { + return fmt.Errorf("%v is not an absolute path", path) + } + return nil +} + +// Check whether pathB is nested whithin pathA +func nestedValid(os, pathA, pathB string) (bool, error) { + if pathA == pathB { + return false, nil + } + if pathA == "/" && pathB != "" { + return true, nil + } + + var sep string + if os == "windows" { + sep = "\\" + } else { + sep = "/" + } + + splitedPathA := strings.Split(filepath.Clean(pathA), sep) + splitedPathB := strings.Split(filepath.Clean(pathB), sep) + lenA := len(splitedPathA) + lenB := len(splitedPathB) + + if lenA > lenB { + if (lenA - lenB) == 1 { + // if pathA is longer but not end with separator + if splitedPathA[lenA-1] != "" { + return false, nil + } + splitedPathA = splitedPathA[:lenA-1] + } else { + return false, nil + } + } + + for i, partA := range splitedPathA { + if partA != splitedPathB[i] { + return false, nil + } + } + + return true, nil +} + func deviceValid(d rspec.LinuxDevice) bool { switch d.Type { case "b", "c", "u": @@ -709,7 +936,6 @@ func deviceValid(d rspec.LinuxDevice) bool { func seccompActionValid(secc rspec.LinuxSeccompAction) bool { switch secc { - case "": case rspec.ActKill: case rspec.ActTrap: case rspec.ActErrno: @@ -750,38 +976,38 @@ func isStructPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct } -func checkMandatoryUnit(field reflect.Value, tagField reflect.StructField, parent string) (msgs []string) { +func checkMandatoryUnit(field reflect.Value, tagField reflect.StructField, parent string) (errs error) { mandatory := !strings.Contains(tagField.Tag.Get("json"), "omitempty") switch field.Kind() { case reflect.Ptr: if mandatory && field.IsNil() { - msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name)) + errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name)) } case reflect.String: if mandatory && (field.Len() == 0) { - msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name)) + errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name)) } case reflect.Slice: if mandatory && (field.IsNil() || field.Len() == 0) { - msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name)) + errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name)) return } for index := 0; index < field.Len(); index++ { mValue := field.Index(index) if mValue.CanInterface() { - msgs = append(msgs, checkMandatory(mValue.Interface())...) + errs = multierror.Append(errs, checkMandatory(mValue.Interface())) } } case reflect.Map: if mandatory && (field.IsNil() || field.Len() == 0) { - msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty.", parent, tagField.Name)) - return msgs + errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", parent, tagField.Name)) + return } keys := field.MapKeys() for index := 0; index < len(keys); index++ { mValue := field.MapIndex(keys[index]) if mValue.CanInterface() { - msgs = append(msgs, checkMandatory(mValue.Interface())...) + errs = multierror.Append(errs, checkMandatory(mValue.Interface())) } } default: @@ -790,7 +1016,7 @@ func checkMandatoryUnit(field reflect.Value, tagField reflect.StructField, paren return } -func checkMandatory(obj interface{}) (msgs []string) { +func checkMandatory(obj interface{}) (errs error) { objT := reflect.TypeOf(obj) objV := reflect.ValueOf(obj) if isStructPtr(objT) { @@ -804,12 +1030,12 @@ func checkMandatory(obj interface{}) (msgs []string) { t := objT.Field(i).Type if isStructPtr(t) && objV.Field(i).IsNil() { if !strings.Contains(objT.Field(i).Tag.Get("json"), "omitempty") { - msgs = append(msgs, fmt.Sprintf("'%s.%s' should not be empty", objT.Name(), objT.Field(i).Name)) + errs = multierror.Append(errs, fmt.Errorf("'%s.%s' should not be empty", objT.Name(), objT.Field(i).Name)) } } else if (isStruct(t) || isStructPtr(t)) && objV.Field(i).CanInterface() { - msgs = append(msgs, checkMandatory(objV.Field(i).Interface())...) + errs = multierror.Append(errs, checkMandatory(objV.Field(i).Interface())) } else { - msgs = append(msgs, checkMandatoryUnit(objV.Field(i), objT.Field(i), objT.Name())...) + errs = multierror.Append(errs, checkMandatoryUnit(objV.Field(i), objT.Field(i), objT.Name())) } } @@ -817,7 +1043,7 @@ func checkMandatory(obj interface{}) (msgs []string) { } // CheckMandatoryFields checks mandatory field of container's config file -func (v *Validator) CheckMandatoryFields() []string { +func (v *Validator) CheckMandatoryFields() error { logrus.Debugf("check mandatory fields") return checkMandatory(v.spec)