Bump containerd to d97a907f

Signed-off-by: John Howard <jhoward@microsoft.com>
This commit is contained in:
John Howard 2018-09-18 09:05:20 -07:00
parent d6a7c22f7b
commit e57b2a8066
70 changed files with 3725 additions and 1363 deletions

View file

@ -115,12 +115,12 @@ github.com/googleapis/gax-go v2.0.0
google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9
# containerd
github.com/containerd/containerd v1.2.0-beta.2
github.com/containerd/containerd d97a907f7f781c0ab8340877d8e6b53cc7f1c2f6
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
github.com/containerd/continuity c7c5070e6f6e090ab93b0a61eb921f2196fc3383
github.com/containerd/continuity f44b615e492bdfb371aae2f76ec694d9da1db537
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
github.com/containerd/go-runc edcf3de1f4971445c42d61f20d506b30612aa031
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
github.com/containerd/ttrpc 94dde388801693c54f88a6596f713b51a8b30b2d
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef

View file

@ -2,6 +2,7 @@
[![GoDoc](https://godoc.org/github.com/containerd/containerd?status.svg)](https://godoc.org/github.com/containerd/containerd)
[![Build Status](https://travis-ci.org/containerd/containerd.svg?branch=master)](https://travis-ci.org/containerd/containerd)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/containerd/containerd?branch=master&svg=true)](https://ci.appveyor.com/project/mlaventure/containerd-3g73f?branch=master)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd?ref=badge_shield)
[![Go Report Card](https://goreportcard.com/badge/github.com/containerd/containerd)](https://goreportcard.com/report/github.com/containerd/containerd)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1271/badge)](https://bestpractices.coreinfrastructure.org/projects/1271)
@ -223,7 +224,7 @@ This will be the best place to discuss design and implementation.
For sync communication we have a community slack with a #containerd channel that everyone is welcome to join and chat about development.
**Slack:** https://dockr.ly/community
**Slack:** https://join.slack.com/t/dockercommunity/shared_invite/enQtNDM4NjAwNDMyOTUwLWZlMDZmYWRjZjk4Zjc5ZGQ5NWZkOWI1Yjk2NGE3ZWVlYjYxM2VhYjczOWIyZDFhZTE3NTUwZWQzMjhmNGYyZTg
### Reporting security issues

View file

@ -141,7 +141,7 @@ type EventsClient interface {
// Forward sends an event that has already been packaged into an envelope
// with a timestamp and namespace.
//
// This is useful if earlier timestamping is required or when fowarding on
// This is useful if earlier timestamping is required or when forwarding on
// behalf of another component, namespace or publisher.
Forward(ctx context.Context, in *ForwardRequest, opts ...grpc.CallOption) (*google_protobuf2.Empty, error)
// Subscribe to a stream of events, possibly returning only that match any
@ -223,7 +223,7 @@ type EventsServer interface {
// Forward sends an event that has already been packaged into an envelope
// with a timestamp and namespace.
//
// This is useful if earlier timestamping is required or when fowarding on
// This is useful if earlier timestamping is required or when forwarding on
// behalf of another component, namespace or publisher.
Forward(context.Context, *ForwardRequest) (*google_protobuf2.Empty, error)
// Subscribe to a stream of events, possibly returning only that match any

View file

@ -20,7 +20,7 @@ service Events {
// Forward sends an event that has already been packaged into an envelope
// with a timestamp and namespace.
//
// This is useful if earlier timestamping is required or when fowarding on
// This is useful if earlier timestamping is required or when forwarding on
// behalf of another component, namespace or publisher.
rpc Forward(ForwardRequest) returns (google.protobuf.Empty);

View file

@ -141,6 +141,15 @@ func NewCreator(opts ...Opt) Creator {
if err != nil {
return nil, err
}
if streams.Stdin == nil {
fifos.Stdin = ""
}
if streams.Stdout == nil {
fifos.Stdout = ""
}
if streams.Stderr == nil {
fifos.Stderr = ""
}
return copyIO(fifos, streams)
}
}

View file

@ -20,25 +20,21 @@ package containerd
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"syscall"
"github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/platforms"
"github.com/containerd/containerd/runtime/linux/runctypes"
"github.com/gogo/protobuf/proto"
protobuf "github.com/gogo/protobuf/types"
"github.com/opencontainers/image-spec/identity"
"github.com/opencontainers/image-spec/specs-go/v1"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
@ -105,44 +101,6 @@ func WithCheckpoint(im Image, snapshotKey string) NewContainerOpts {
}
}
// WithTaskCheckpoint allows a task to be created with live runtime and memory data from a
// previous checkpoint. Additional software such as CRIU may be required to
// restore a task from a checkpoint
func WithTaskCheckpoint(im Image) NewTaskOpts {
return func(ctx context.Context, c *Client, info *TaskInfo) error {
desc := im.Target()
id := desc.Digest
index, err := decodeIndex(ctx, c.ContentStore(), desc)
if err != nil {
return err
}
for _, m := range index.Manifests {
if m.MediaType == images.MediaTypeContainerd1Checkpoint {
info.Checkpoint = &types.Descriptor{
MediaType: m.MediaType,
Size_: m.Size,
Digest: m.Digest,
}
return nil
}
}
return fmt.Errorf("checkpoint not found in index %s", id)
}
}
func decodeIndex(ctx context.Context, store content.Provider, desc ocispec.Descriptor) (*v1.Index, error) {
var index v1.Index
p, err := content.ReadBlob(ctx, store, desc)
if err != nil {
return nil, err
}
if err := json.Unmarshal(p, &index); err != nil {
return nil, err
}
return &index, nil
}
// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
// filesystem to be used by a container with user namespaces
func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
@ -221,19 +179,3 @@ func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
return os.Lchown(path, u, g)
}
}
// WithNoPivotRoot instructs the runtime not to you pivot_root
func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
if info.Options == nil {
info.Options = &runctypes.CreateOptions{
NoPivotRoot: true,
}
return nil
}
copts, ok := info.Options.(*runctypes.CreateOptions)
if !ok {
return errors.New("invalid options type, expected runctypes.CreateOptions")
}
copts.NoPivotRoot = true
return nil
}

View file

@ -33,6 +33,8 @@ import (
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters"
"github.com/containerd/containerd/log"
"github.com/containerd/continuity"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
@ -651,5 +653,5 @@ func writeTimestampFile(p string, t time.Time) error {
return err
}
return ioutil.WriteFile(p, b, 0666)
return continuity.AtomicWriteFile(p, b, 0666)
}

View file

@ -132,11 +132,11 @@ func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest,
// clean up!!
defer os.RemoveAll(w.path)
if _, err := os.Stat(target); err == nil {
// collision with the target file!
return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", dgst)
}
if err := os.Rename(ingest, target); err != nil {
if os.IsExist(err) {
// collision with the target file!
return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", dgst)
}
return err
}
commitTime := time.Now()

View file

@ -30,7 +30,7 @@ import (
)
// WithProfile receives the name of a file stored on disk comprising a json
// formated seccomp profile, as specified by the opencontainers/runtime-spec.
// formatted seccomp profile, as specified by the opencontainers/runtime-spec.
// The profile is read from the file, unmarshaled, and set to the spec.
func WithProfile(profile string) oci.SpecOpts {
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {

View file

@ -52,7 +52,7 @@ var _ events.Subscriber = &Exchange{}
// Forward accepts an envelope to be direcly distributed on the exchange.
//
// This is useful when an event is forwaded on behalf of another namespace or
// This is useful when an event is forwarded on behalf of another namespace or
// when the event is propagated on behalf of another publisher.
func (e *Exchange) Forward(ctx context.Context, envelope *events.Envelope) (err error) {
if err := validateEnvelope(envelope); err != nil {

57
vendor/github.com/containerd/containerd/export.go generated vendored Normal file
View file

@ -0,0 +1,57 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package containerd
import (
"context"
"io"
"github.com/containerd/containerd/images"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
type exportOpts struct {
}
// ExportOpt allows the caller to specify export-specific options
type ExportOpt func(c *exportOpts) error
func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) {
var eopts exportOpts
for _, o := range opts {
if err := o(&eopts); err != nil {
return eopts, err
}
}
return eopts, nil
}
// Export exports an image to a Tar stream.
// OCI format is used by default.
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream.
func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) {
_, err := resolveExportOpt(opts...) // unused now
if err != nil {
return nil, err
}
pr, pw := io.Pipe()
go func() {
pw.CloseWithError(exporter.Export(ctx, c.ContentStore(), desc, pw))
}()
return pr, nil
}

View file

@ -22,7 +22,6 @@ import (
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
type importOpts struct {
@ -84,35 +83,3 @@ func (c *Client) Import(ctx context.Context, importer images.Importer, reader io
}
return images, nil
}
type exportOpts struct {
}
// ExportOpt allows the caller to specify export-specific options
type ExportOpt func(c *exportOpts) error
func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) {
var eopts exportOpts
for _, o := range opts {
if err := o(&eopts); err != nil {
return eopts, err
}
}
return eopts, nil
}
// Export exports an image to a Tar stream.
// OCI format is used by default.
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream.
func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) {
_, err := resolveExportOpt(opts...) // unused now
if err != nil {
return nil, err
}
pr, pw := io.Pipe()
go func() {
pw.CloseWithError(exporter.Export(ctx, c.ContentStore(), desc, pw))
}()
return pr, nil
}

View file

@ -33,25 +33,14 @@ import (
// Install a binary image into the opt service
func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts) error {
resp, err := c.IntrospectionService().Plugins(ctx, &introspectionapi.PluginsRequest{
Filters: []string{
"id==opt",
},
})
if err != nil {
return err
}
if len(resp.Plugins) != 1 {
return errors.New("opt service not enabled")
}
path := resp.Plugins[0].Exports["path"]
if path == "" {
return errors.New("opt path not exported")
}
var config InstallConfig
for _, o := range opts {
o(&config)
}
path, err := c.getInstallPath(ctx, config)
if err != nil {
return err
}
var (
cs = image.ContentStore()
platform = platforms.Default()
@ -89,3 +78,25 @@ func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts)
}
return nil
}
func (c *Client) getInstallPath(ctx context.Context, config InstallConfig) (string, error) {
if config.Path != "" {
return config.Path, nil
}
resp, err := c.IntrospectionService().Plugins(ctx, &introspectionapi.PluginsRequest{
Filters: []string{
"id==opt",
},
})
if err != nil {
return "", err
}
if len(resp.Plugins) != 1 {
return "", errors.New("opt service not enabled")
}
path := resp.Plugins[0].Exports["path"]
if path == "" {
return "", errors.New("opt path not exported")
}
return path, nil
}

View file

@ -25,6 +25,8 @@ type InstallConfig struct {
Libs bool
// Replace will overwrite existing binaries or libs in the opt directory
Replace bool
// Path to install libs and binaries to
Path string
}
// WithInstallLibs installs libs from the image
@ -36,3 +38,10 @@ func WithInstallLibs(c *InstallConfig) {
func WithInstallReplace(c *InstallConfig) {
c.Replace = true
}
// WithInstallPath sets the optional install path
func WithInstallPath(path string) InstallOpts {
return func(c *InstallConfig) {
c.Path = path
}
}

View file

@ -19,8 +19,8 @@ package metadata
import (
"context"
"github.com/boltdb/bolt"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
type transactionKey struct{}

View file

@ -19,8 +19,8 @@ package boltutil
import (
"time"
"github.com/boltdb/bolt"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
var (

View file

@ -17,11 +17,11 @@
package metadata
import (
"github.com/boltdb/bolt"
digest "github.com/opencontainers/go-digest"
bolt "go.etcd.io/bbolt"
)
// The layout where a "/" delineates a bucket is desribed in the following
// The layout where a "/" delineates a bucket is described in the following
// section. Please try to follow this as closely as possible when adding
// functionality. We can bolster this with helpers and more structure if that
// becomes an issue.
@ -164,11 +164,11 @@ func getSnapshotterBucket(tx *bolt.Tx, namespace, snapshotter string) *bolt.Buck
}
func createBlobBucket(tx *bolt.Tx, namespace string, dgst digest.Digest) (*bolt.Bucket, error) {
bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectBlob, []byte(dgst.String()))
bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectBlob)
if err != nil {
return nil, err
}
return bkt, nil
return bkt.CreateBucket([]byte(dgst.String()))
}
func getBlobsBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {

View file

@ -21,7 +21,6 @@ import (
"strings"
"time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters"
@ -32,6 +31,7 @@ import (
"github.com/gogo/protobuf/proto"
"github.com/gogo/protobuf/types"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
type containerStore struct {

View file

@ -23,7 +23,6 @@ import (
"sync"
"time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters"
@ -34,6 +33,7 @@ import (
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
type contentStore struct {
@ -592,9 +592,6 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64,
}
size = nw.desc.Size
actual = nw.desc.Digest
if getBlobBucket(tx, nw.namespace, actual) != nil {
return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual)
}
} else {
status, err := nw.w.Status()
if err != nil {
@ -606,18 +603,16 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64,
size = status.Offset
actual = nw.w.Digest()
if err := nw.w.Commit(ctx, size, expected); err != nil {
if !errdefs.IsAlreadyExists(err) {
return "", err
}
if getBlobBucket(tx, nw.namespace, actual) != nil {
return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual)
}
if err := nw.w.Commit(ctx, size, expected); err != nil && !errdefs.IsAlreadyExists(err) {
return "", err
}
}
bkt, err := createBlobBucket(tx, nw.namespace, actual)
if err != nil {
if err == bolt.ErrBucketExists {
return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual)
}
return "", err
}

View file

@ -23,12 +23,12 @@ import (
"sync"
"time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/gc"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/snapshots"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
const (
@ -43,7 +43,7 @@ const (
// dbVersion represents updates to the schema
// version which are additions and compatible with
// prior version of the same schema.
dbVersion = 2
dbVersion = 3
)
// DB represents a metadata database backed by a bolt

View file

@ -23,10 +23,10 @@ import (
"strings"
"time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/gc"
"github.com/containerd/containerd/log"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
const (

View file

@ -23,7 +23,6 @@ import (
"strings"
"time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters"
"github.com/containerd/containerd/images"
@ -33,6 +32,7 @@ import (
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
type imageStore struct {

View file

@ -20,7 +20,6 @@ import (
"context"
"time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters"
"github.com/containerd/containerd/leases"
@ -28,6 +27,7 @@ import (
"github.com/containerd/containerd/namespaces"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
// LeaseManager manages the create/delete lifecyle of leases

View file

@ -16,7 +16,7 @@
package metadata
import "github.com/boltdb/bolt"
import bolt "go.etcd.io/bbolt"
type migration struct {
schema string
@ -45,6 +45,11 @@ var migrations = []migration{
version: 2,
migrate: migrateIngests,
},
{
schema: "v1",
version: 3,
migrate: noOpMigration,
},
}
// addChildLinks Adds children key to the snapshotters to enforce snapshot
@ -154,3 +159,10 @@ func migrateIngests(tx *bolt.Tx) error {
return nil
}
// noOpMigration was for a database change from boltdb/bolt which is no
// longer being supported, to go.etcd.io/bbolt which is the currently
// maintained repo for boltdb.
func noOpMigration(tx *bolt.Tx) error {
return nil
}

View file

@ -19,11 +19,11 @@ package metadata
import (
"context"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs"
l "github.com/containerd/containerd/labels"
"github.com/containerd/containerd/namespaces"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
type namespaceStore struct {

View file

@ -23,7 +23,6 @@ import (
"sync"
"time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/labels"
"github.com/containerd/containerd/log"
@ -32,6 +31,7 @@ import (
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/snapshots"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
type snapshotter struct {

View file

@ -32,6 +32,10 @@ var (
// Mount to the provided target
func (m *Mount) Mount(target string) error {
if m.Type != "windows-layer" {
return errors.Errorf("invalid windows mount type: '%s'", m.Type)
}
home, layerID := filepath.Split(m.Source)
parentLayerPaths, err := m.GetParentPaths()

View file

@ -18,11 +18,27 @@ package oci
import (
"context"
"path/filepath"
"runtime"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/platforms"
"github.com/containerd/containerd/containers"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
const (
rwm = "rwm"
defaultRootfsPath = "rootfs"
)
var (
defaultUnixEnv = []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
}
)
// Spec is a type alias to the OCI runtime spec to allow third part SpecOpts
// to be created without the "issues" with go vendoring and package imports
type Spec = specs.Spec
@ -30,12 +46,36 @@ type Spec = specs.Spec
// GenerateSpec will generate a default spec from the provided image
// for use as a containerd container
func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
s, err := createDefaultSpec(ctx, c.ID)
if err != nil {
return GenerateSpecWithPlatform(ctx, client, platforms.DefaultString(), c, opts...)
}
// GenerateSpecWithPlatform will generate a default spec from the provided image
// for use as a containerd container in the platform requested.
func GenerateSpecWithPlatform(ctx context.Context, client Client, platform string, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
var s Spec
if err := generateDefaultSpecWithPlatform(ctx, platform, c.ID, &s); err != nil {
return nil, err
}
return s, ApplyOpts(ctx, client, c, s, opts...)
return &s, ApplyOpts(ctx, client, c, &s, opts...)
}
func generateDefaultSpecWithPlatform(ctx context.Context, platform, id string, s *Spec) error {
plat, err := platforms.Parse(platform)
if err != nil {
return err
}
if plat.OS == "windows" {
err = populateDefaultWindowsSpec(ctx, s, id)
} else {
err = populateDefaultUnixSpec(ctx, s, id)
if err == nil && runtime.GOOS == "windows" {
// To run LCOW we have a Linux and Windows section. Add an empty one now.
s.Windows = &specs.Windows{}
}
}
return err
}
// ApplyOpts applys the options to the given spec, injecting data from the
@ -50,7 +90,173 @@ func ApplyOpts(ctx context.Context, client Client, c *containers.Container, s *S
return nil
}
func createDefaultSpec(ctx context.Context, id string) (*Spec, error) {
var s Spec
return &s, populateDefaultSpec(ctx, &s, id)
func defaultUnixCaps() []string {
return []string{
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE",
}
}
func defaultUnixNamespaces() []specs.LinuxNamespace {
return []specs.LinuxNamespace{
{
Type: specs.PIDNamespace,
},
{
Type: specs.IPCNamespace,
},
{
Type: specs.UTSNamespace,
},
{
Type: specs.MountNamespace,
},
{
Type: specs.NetworkNamespace,
},
}
}
func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return err
}
*s = Spec{
Version: specs.Version,
Root: &specs.Root{
Path: defaultRootfsPath,
},
Process: &specs.Process{
Env: defaultUnixEnv,
Cwd: "/",
NoNewPrivileges: true,
User: specs.User{
UID: 0,
GID: 0,
},
Capabilities: &specs.LinuxCapabilities{
Bounding: defaultUnixCaps(),
Permitted: defaultUnixCaps(),
Inheritable: defaultUnixCaps(),
Effective: defaultUnixCaps(),
},
Rlimits: []specs.POSIXRlimit{
{
Type: "RLIMIT_NOFILE",
Hard: uint64(1024),
Soft: uint64(1024),
},
},
},
Mounts: []specs.Mount{
{
Destination: "/proc",
Type: "proc",
Source: "proc",
},
{
Destination: "/dev",
Type: "tmpfs",
Source: "tmpfs",
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
},
{
Destination: "/dev/pts",
Type: "devpts",
Source: "devpts",
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
},
{
Destination: "/dev/shm",
Type: "tmpfs",
Source: "shm",
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
},
{
Destination: "/dev/mqueue",
Type: "mqueue",
Source: "mqueue",
Options: []string{"nosuid", "noexec", "nodev"},
},
{
Destination: "/sys",
Type: "sysfs",
Source: "sysfs",
Options: []string{"nosuid", "noexec", "nodev", "ro"},
},
{
Destination: "/run",
Type: "tmpfs",
Source: "tmpfs",
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
},
},
Linux: &specs.Linux{
MaskedPaths: []string{
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/sys/firmware",
"/proc/scsi",
},
ReadonlyPaths: []string{
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger",
},
CgroupsPath: filepath.Join("/", ns, id),
Resources: &specs.LinuxResources{
Devices: []specs.LinuxDeviceCgroup{
{
Allow: false,
Access: rwm,
},
},
},
Namespaces: defaultUnixNamespaces(),
},
}
return nil
}
func populateDefaultWindowsSpec(ctx context.Context, s *Spec, id string) error {
*s = Spec{
Version: specs.Version,
Root: &specs.Root{},
Process: &specs.Process{
Cwd: `C:\`,
ConsoleSize: &specs.Box{
Width: 80,
Height: 20,
},
},
Windows: &specs.Windows{
IgnoreFlushesDuringBoot: true,
Network: &specs.WindowsNetwork{
AllowUnqualifiedDNSQuery: true,
},
},
}
return nil
}

View file

@ -19,12 +19,25 @@ package oci
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/platforms"
"github.com/containerd/continuity/fs"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runc/libcontainer/user"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/syndtr/gocapability/capability"
)
// SpecOpts sets spec specific information to a newly generated OCI spec
@ -49,13 +62,45 @@ func setProcess(s *Spec) {
}
}
// setRoot sets Root to empty if unset
func setRoot(s *Spec) {
if s.Root == nil {
s.Root = &specs.Root{}
}
}
// setLinux sets Linux to empty if unset
func setLinux(s *Spec) {
if s.Linux == nil {
s.Linux = &specs.Linux{}
}
}
// setCapabilities sets Linux Capabilities to empty if unset
func setCapabilities(s *Spec) {
setProcess(s)
if s.Process.Capabilities == nil {
s.Process.Capabilities = &specs.LinuxCapabilities{}
}
}
// WithDefaultSpec returns a SpecOpts that will populate the spec with default
// values.
//
// Use as the first option to clear the spec, then apply options afterwards.
func WithDefaultSpec() SpecOpts {
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
return populateDefaultSpec(ctx, s, c.ID)
return generateDefaultSpecWithPlatform(ctx, platforms.DefaultString(), c.ID, s)
}
}
// WithDefaultSpecForPlatform returns a SpecOpts that will populate the spec
// with default values for a given platform.
//
// Use as the first option to clear the spec, then apply options afterwards.
func WithDefaultSpecForPlatform(platform string) SpecOpts {
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
return generateDefaultSpecWithPlatform(ctx, platform, c.ID, s)
}
}
@ -81,32 +126,6 @@ func WithSpecFromFile(filename string) SpecOpts {
}
}
// WithProcessArgs replaces the args on the generated spec
func WithProcessArgs(args ...string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.Args = args
return nil
}
}
// WithProcessCwd replaces the current working directory on the generated spec
func WithProcessCwd(cwd string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.Cwd = cwd
return nil
}
}
// WithHostname sets the container's hostname
func WithHostname(name string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
s.Hostname = name
return nil
}
}
// WithEnv appends environment variables
func WithEnv(environmentVariables []string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
@ -118,14 +137,6 @@ func WithEnv(environmentVariables []string) SpecOpts {
}
}
// WithMounts appends mounts
func WithMounts(mounts []specs.Mount) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
s.Mounts = append(s.Mounts, mounts...)
return nil
}
}
// replaceOrAppendEnvValues returns the defaults with the overrides either
// replaced by env key or appended to the list
func replaceOrAppendEnvValues(defaults, overrides []string) []string {
@ -163,3 +174,832 @@ func replaceOrAppendEnvValues(defaults, overrides []string) []string {
return defaults
}
// WithProcessArgs replaces the args on the generated spec
func WithProcessArgs(args ...string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.Args = args
return nil
}
}
// WithProcessCwd replaces the current working directory on the generated spec
func WithProcessCwd(cwd string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.Cwd = cwd
return nil
}
}
// WithTTY sets the information on the spec as well as the environment variables for
// using a TTY
func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.Terminal = true
if s.Linux != nil {
s.Process.Env = append(s.Process.Env, "TERM=xterm")
}
return nil
}
// WithTTYSize sets the information on the spec as well as the environment variables for
// using a TTY
func WithTTYSize(width, height int) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
if s.Process.ConsoleSize == nil {
s.Process.ConsoleSize = &specs.Box{}
}
s.Process.ConsoleSize.Width = uint(width)
s.Process.ConsoleSize.Height = uint(height)
return nil
}
}
// WithHostname sets the container's hostname
func WithHostname(name string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
s.Hostname = name
return nil
}
}
// WithMounts appends mounts
func WithMounts(mounts []specs.Mount) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
s.Mounts = append(s.Mounts, mounts...)
return nil
}
}
// WithHostNamespace allows a task to run inside the host's linux namespace
func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
for i, n := range s.Linux.Namespaces {
if n.Type == ns {
s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
return nil
}
}
return nil
}
}
// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the
// spec, the existing namespace is replaced by the one provided.
func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
for i, n := range s.Linux.Namespaces {
if n.Type == ns.Type {
before := s.Linux.Namespaces[:i]
after := s.Linux.Namespaces[i+1:]
s.Linux.Namespaces = append(before, ns)
s.Linux.Namespaces = append(s.Linux.Namespaces, after...)
return nil
}
}
s.Linux.Namespaces = append(s.Linux.Namespaces, ns)
return nil
}
}
// WithImageConfig configures the spec to from the configuration of an Image
func WithImageConfig(image Image) SpecOpts {
return WithImageConfigArgs(image, nil)
}
// WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that
// replaces the CMD of the image
func WithImageConfigArgs(image Image, args []string) SpecOpts {
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
ic, err := image.Config(ctx)
if err != nil {
return err
}
var (
ociimage v1.Image
config v1.ImageConfig
)
switch ic.MediaType {
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
if err != nil {
return err
}
if err := json.Unmarshal(p, &ociimage); err != nil {
return err
}
config = ociimage.Config
default:
return fmt.Errorf("unknown image config media type %s", ic.MediaType)
}
setProcess(s)
if s.Linux != nil {
s.Process.Env = append(s.Process.Env, config.Env...)
cmd := config.Cmd
if len(args) > 0 {
cmd = args
}
s.Process.Args = append(config.Entrypoint, cmd...)
cwd := config.WorkingDir
if cwd == "" {
cwd = "/"
}
s.Process.Cwd = cwd
if config.User != "" {
if err := WithUser(config.User)(ctx, client, c, s); err != nil {
return err
}
return WithAdditionalGIDs(fmt.Sprintf("%d", s.Process.User.UID))(ctx, client, c, s)
}
// we should query the image's /etc/group for additional GIDs
// even if there is no specified user in the image config
return WithAdditionalGIDs("root")(ctx, client, c, s)
} else if s.Windows != nil {
s.Process.Env = config.Env
s.Process.Args = append(config.Entrypoint, config.Cmd...)
s.Process.User = specs.User{
Username: config.User,
}
} else {
return errors.New("spec does not contain Linux or Windows section")
}
return nil
}
}
// WithRootFSPath specifies unmanaged rootfs path.
func WithRootFSPath(path string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setRoot(s)
s.Root.Path = path
// Entrypoint is not set here (it's up to caller)
return nil
}
}
// WithRootFSReadonly sets specs.Root.Readonly to true
func WithRootFSReadonly() SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setRoot(s)
s.Root.Readonly = true
return nil
}
}
// WithNoNewPrivileges sets no_new_privileges on the process for the container
func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.NoNewPrivileges = true
return nil
}
// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
s.Mounts = append(s.Mounts, specs.Mount{
Destination: "/etc/hosts",
Type: "bind",
Source: "/etc/hosts",
Options: []string{"rbind", "ro"},
})
return nil
}
// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
s.Mounts = append(s.Mounts, specs.Mount{
Destination: "/etc/resolv.conf",
Type: "bind",
Source: "/etc/resolv.conf",
Options: []string{"rbind", "ro"},
})
return nil
}
// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
s.Mounts = append(s.Mounts, specs.Mount{
Destination: "/etc/localtime",
Type: "bind",
Source: "/etc/localtime",
Options: []string{"rbind", "ro"},
})
return nil
}
// WithUserNamespace sets the uid and gid mappings for the task
// this can be called multiple times to add more mappings to the generated spec
func WithUserNamespace(container, host, size uint32) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
var hasUserns bool
setLinux(s)
for _, ns := range s.Linux.Namespaces {
if ns.Type == specs.UserNamespace {
hasUserns = true
break
}
}
if !hasUserns {
s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
Type: specs.UserNamespace,
})
}
mapping := specs.LinuxIDMapping{
ContainerID: container,
HostID: host,
Size: size,
}
s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping)
s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping)
return nil
}
}
// WithCgroup sets the container's cgroup path
func WithCgroup(path string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
s.Linux.CgroupsPath = path
return nil
}
}
// WithNamespacedCgroup uses the namespace set on the context to create a
// root directory for containers in the cgroup with the id as the subcgroup
func WithNamespacedCgroup() SpecOpts {
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return err
}
setLinux(s)
s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
return nil
}
}
// WithUser sets the user to be used within the container.
// It accepts a valid user string in OCI Image Spec v1.0.0:
// user, uid, user:group, uid:gid, uid:group, user:gid
func WithUser(userstr string) SpecOpts {
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
setProcess(s)
parts := strings.Split(userstr, ":")
switch len(parts) {
case 1:
v, err := strconv.Atoi(parts[0])
if err != nil {
// if we cannot parse as a uint they try to see if it is a username
return WithUsername(userstr)(ctx, client, c, s)
}
return WithUserID(uint32(v))(ctx, client, c, s)
case 2:
var (
username string
groupname string
)
var uid, gid uint32
v, err := strconv.Atoi(parts[0])
if err != nil {
username = parts[0]
} else {
uid = uint32(v)
}
if v, err = strconv.Atoi(parts[1]); err != nil {
groupname = parts[1]
} else {
gid = uint32(v)
}
if username == "" && groupname == "" {
s.Process.User.UID, s.Process.User.GID = uid, gid
return nil
}
f := func(root string) error {
if username != "" {
user, err := getUserFromPath(root, func(u user.User) bool {
return u.Name == username
})
if err != nil {
return err
}
uid = uint32(user.Uid)
}
if groupname != "" {
gid, err = getGIDFromPath(root, func(g user.Group) bool {
return g.Name == groupname
})
if err != nil {
return err
}
}
s.Process.User.UID, s.Process.User.GID = uid, gid
return nil
}
if c.Snapshotter == "" && c.SnapshotKey == "" {
if !isRootfsAbs(s.Root.Path) {
return errors.New("rootfs absolute path is required")
}
return f(s.Root.Path)
}
if c.Snapshotter == "" {
return errors.New("no snapshotter set for container")
}
if c.SnapshotKey == "" {
return errors.New("rootfs snapshot not created for container")
}
snapshotter := client.SnapshotService(c.Snapshotter)
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
if err != nil {
return err
}
return mount.WithTempMount(ctx, mounts, f)
default:
return fmt.Errorf("invalid USER value %s", userstr)
}
}
}
// WithUIDGID allows the UID and GID for the Process to be set
func WithUIDGID(uid, gid uint32) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.User.UID = uid
s.Process.User.GID = gid
return nil
}
}
// WithUserID sets the correct UID and GID for the container based
// on the image's /etc/passwd contents. If /etc/passwd does not exist,
// or uid is not found in /etc/passwd, it sets the requested uid,
// additionally sets the gid to 0, and does not return an error.
func WithUserID(uid uint32) SpecOpts {
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
setProcess(s)
if c.Snapshotter == "" && c.SnapshotKey == "" {
if !isRootfsAbs(s.Root.Path) {
return errors.Errorf("rootfs absolute path is required")
}
user, err := getUserFromPath(s.Root.Path, func(u user.User) bool {
return u.Uid == int(uid)
})
if err != nil {
if os.IsNotExist(err) || err == errNoUsersFound {
s.Process.User.UID, s.Process.User.GID = uid, 0
return nil
}
return err
}
s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid)
return nil
}
if c.Snapshotter == "" {
return errors.Errorf("no snapshotter set for container")
}
if c.SnapshotKey == "" {
return errors.Errorf("rootfs snapshot not created for container")
}
snapshotter := client.SnapshotService(c.Snapshotter)
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
if err != nil {
return err
}
return mount.WithTempMount(ctx, mounts, func(root string) error {
user, err := getUserFromPath(root, func(u user.User) bool {
return u.Uid == int(uid)
})
if err != nil {
if os.IsNotExist(err) || err == errNoUsersFound {
s.Process.User.UID, s.Process.User.GID = uid, 0
return nil
}
return err
}
s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid)
return nil
})
}
}
// WithUsername sets the correct UID and GID for the container
// based on the the image's /etc/passwd contents. If /etc/passwd
// does not exist, or the username is not found in /etc/passwd,
// it returns error.
func WithUsername(username string) SpecOpts {
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
setProcess(s)
if s.Linux != nil {
if c.Snapshotter == "" && c.SnapshotKey == "" {
if !isRootfsAbs(s.Root.Path) {
return errors.Errorf("rootfs absolute path is required")
}
user, err := getUserFromPath(s.Root.Path, func(u user.User) bool {
return u.Name == username
})
if err != nil {
return err
}
s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid)
return nil
}
if c.Snapshotter == "" {
return errors.Errorf("no snapshotter set for container")
}
if c.SnapshotKey == "" {
return errors.Errorf("rootfs snapshot not created for container")
}
snapshotter := client.SnapshotService(c.Snapshotter)
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
if err != nil {
return err
}
return mount.WithTempMount(ctx, mounts, func(root string) error {
user, err := getUserFromPath(root, func(u user.User) bool {
return u.Name == username
})
if err != nil {
return err
}
s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid)
return nil
})
} else if s.Windows != nil {
s.Process.User.Username = username
} else {
return errors.New("spec does not contain Linux or Windows section")
}
return nil
}
}
// WithAdditionalGIDs sets the OCI spec's additionalGids array to any additional groups listed
// for a particular user in the /etc/groups file of the image's root filesystem
// The passed in user can be either a uid or a username.
func WithAdditionalGIDs(userstr string) SpecOpts {
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
setProcess(s)
setAdditionalGids := func(root string) error {
var username string
uid, err := strconv.Atoi(userstr)
if err == nil {
user, err := getUserFromPath(root, func(u user.User) bool {
return u.Uid == uid
})
if err != nil {
if os.IsNotExist(err) || err == errNoUsersFound {
return nil
}
return err
}
username = user.Name
} else {
username = userstr
}
gids, err := getSupplementalGroupsFromPath(root, func(g user.Group) bool {
// we only want supplemental groups
if g.Name == username {
return false
}
for _, entry := range g.List {
if entry == username {
return true
}
}
return false
})
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
s.Process.User.AdditionalGids = gids
return nil
}
if c.Snapshotter == "" && c.SnapshotKey == "" {
if !isRootfsAbs(s.Root.Path) {
return errors.Errorf("rootfs absolute path is required")
}
return setAdditionalGids(s.Root.Path)
}
if c.Snapshotter == "" {
return errors.Errorf("no snapshotter set for container")
}
if c.SnapshotKey == "" {
return errors.Errorf("rootfs snapshot not created for container")
}
snapshotter := client.SnapshotService(c.Snapshotter)
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
if err != nil {
return err
}
return mount.WithTempMount(ctx, mounts, setAdditionalGids)
}
}
// WithCapabilities sets Linux capabilities on the process
func WithCapabilities(caps []string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setCapabilities(s)
s.Process.Capabilities.Bounding = caps
s.Process.Capabilities.Effective = caps
s.Process.Capabilities.Permitted = caps
s.Process.Capabilities.Inheritable = caps
return nil
}
}
// WithAllCapabilities sets all linux capabilities for the process
var WithAllCapabilities = WithCapabilities(getAllCapabilities())
func getAllCapabilities() []string {
last := capability.CAP_LAST_CAP
// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
if last == capability.Cap(63) {
last = capability.CAP_BLOCK_SUSPEND
}
var caps []string
for _, cap := range capability.List() {
if cap > last {
continue
}
caps = append(caps, "CAP_"+strings.ToUpper(cap.String()))
}
return caps
}
// WithAmbientCapabilities set the Linux ambient capabilities for the process
// Ambient capabilities should only be set for non-root users or the caller should
// understand how these capabilities are used and set
func WithAmbientCapabilities(caps []string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setCapabilities(s)
s.Process.Capabilities.Ambient = caps
return nil
}
}
var errNoUsersFound = errors.New("no users found")
func getUserFromPath(root string, filter func(user.User) bool) (user.User, error) {
ppath, err := fs.RootPath(root, "/etc/passwd")
if err != nil {
return user.User{}, err
}
users, err := user.ParsePasswdFileFilter(ppath, filter)
if err != nil {
return user.User{}, err
}
if len(users) == 0 {
return user.User{}, errNoUsersFound
}
return users[0], nil
}
var errNoGroupsFound = errors.New("no groups found")
func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
gpath, err := fs.RootPath(root, "/etc/group")
if err != nil {
return 0, err
}
groups, err := user.ParseGroupFileFilter(gpath, filter)
if err != nil {
return 0, err
}
if len(groups) == 0 {
return 0, errNoGroupsFound
}
g := groups[0]
return uint32(g.Gid), nil
}
func getSupplementalGroupsFromPath(root string, filter func(user.Group) bool) ([]uint32, error) {
gpath, err := fs.RootPath(root, "/etc/group")
if err != nil {
return []uint32{}, err
}
groups, err := user.ParseGroupFileFilter(gpath, filter)
if err != nil {
return []uint32{}, err
}
if len(groups) == 0 {
// if there are no additional groups; just return an empty set
return []uint32{}, nil
}
addlGids := []uint32{}
for _, grp := range groups {
addlGids = append(addlGids, uint32(grp.Gid))
}
return addlGids, nil
}
func isRootfsAbs(root string) bool {
return filepath.IsAbs(root)
}
// WithMaskedPaths sets the masked paths option
func WithMaskedPaths(paths []string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
s.Linux.MaskedPaths = paths
return nil
}
}
// WithReadonlyPaths sets the read only paths option
func WithReadonlyPaths(paths []string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
s.Linux.ReadonlyPaths = paths
return nil
}
}
// WithWriteableSysfs makes any sysfs mounts writeable
func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
for i, m := range s.Mounts {
if m.Type == "sysfs" {
var options []string
for _, o := range m.Options {
if o == "ro" {
o = "rw"
}
options = append(options, o)
}
s.Mounts[i].Options = options
}
}
return nil
}
// WithWriteableCgroupfs makes any cgroup mounts writeable
func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
for i, m := range s.Mounts {
if m.Type == "cgroup" {
var options []string
for _, o := range m.Options {
if o == "ro" {
o = "rw"
}
options = append(options, o)
}
s.Mounts[i].Options = options
}
}
return nil
}
// WithSelinuxLabel sets the process SELinux label
func WithSelinuxLabel(label string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.SelinuxLabel = label
return nil
}
}
// WithApparmorProfile sets the Apparmor profile for the process
func WithApparmorProfile(profile string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.ApparmorProfile = profile
return nil
}
}
// WithSeccompUnconfined clears the seccomp profile
func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
s.Linux.Seccomp = nil
return nil
}
// WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's
// allowed and denied devices
func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
if s.Linux.Resources == nil {
s.Linux.Resources = &specs.LinuxResources{}
}
s.Linux.Resources.Devices = nil
return nil
}
// WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to
// the container's resource cgroup spec
func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
if s.Linux.Resources == nil {
s.Linux.Resources = &specs.LinuxResources{}
}
intptr := func(i int64) *int64 {
return &i
}
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{
{
// "/dev/null",
Type: "c",
Major: intptr(1),
Minor: intptr(3),
Access: rwm,
Allow: true,
},
{
// "/dev/random",
Type: "c",
Major: intptr(1),
Minor: intptr(8),
Access: rwm,
Allow: true,
},
{
// "/dev/full",
Type: "c",
Major: intptr(1),
Minor: intptr(7),
Access: rwm,
Allow: true,
},
{
// "/dev/tty",
Type: "c",
Major: intptr(5),
Minor: intptr(0),
Access: rwm,
Allow: true,
},
{
// "/dev/zero",
Type: "c",
Major: intptr(1),
Minor: intptr(5),
Access: rwm,
Allow: true,
},
{
// "/dev/urandom",
Type: "c",
Major: intptr(1),
Minor: intptr(9),
Access: rwm,
Allow: true,
},
{
// "/dev/console",
Type: "c",
Major: intptr(5),
Minor: intptr(1),
Access: rwm,
Allow: true,
},
// /dev/pts/ - pts namespaces are "coming soon"
{
Type: "c",
Major: intptr(136),
Access: rwm,
Allow: true,
},
{
Type: "c",
Major: intptr(5),
Minor: intptr(2),
Access: rwm,
Allow: true,
},
{
// tuntap
Type: "c",
Major: intptr(10),
Minor: intptr(200),
Access: rwm,
Allow: true,
},
}...)
return nil
}
// WithPrivileged sets up options for a privileged container
// TODO(justincormack) device handling
var WithPrivileged = Compose(
WithAllCapabilities,
WithMaskedPaths(nil),
WithReadonlyPaths(nil),
WithWriteableSysfs,
WithWriteableCgroupfs,
WithSelinuxLabel(""),
WithApparmorProfile(""),
WithSeccompUnconfined,
)

View file

@ -1,733 +0,0 @@
// +build !windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package oci
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/continuity/fs"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runc/libcontainer/user"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/syndtr/gocapability/capability"
)
// WithTTY sets the information on the spec as well as the environment variables for
// using a TTY
func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.Terminal = true
s.Process.Env = append(s.Process.Env, "TERM=xterm")
return nil
}
// setRoot sets Root to empty if unset
func setRoot(s *Spec) {
if s.Root == nil {
s.Root = &specs.Root{}
}
}
// setLinux sets Linux to empty if unset
func setLinux(s *Spec) {
if s.Linux == nil {
s.Linux = &specs.Linux{}
}
}
// setCapabilities sets Linux Capabilities to empty if unset
func setCapabilities(s *Spec) {
setProcess(s)
if s.Process.Capabilities == nil {
s.Process.Capabilities = &specs.LinuxCapabilities{}
}
}
// WithHostNamespace allows a task to run inside the host's linux namespace
func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
for i, n := range s.Linux.Namespaces {
if n.Type == ns {
s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
return nil
}
}
return nil
}
}
// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the
// spec, the existing namespace is replaced by the one provided.
func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
for i, n := range s.Linux.Namespaces {
if n.Type == ns.Type {
before := s.Linux.Namespaces[:i]
after := s.Linux.Namespaces[i+1:]
s.Linux.Namespaces = append(before, ns)
s.Linux.Namespaces = append(s.Linux.Namespaces, after...)
return nil
}
}
s.Linux.Namespaces = append(s.Linux.Namespaces, ns)
return nil
}
}
// WithImageConfig configures the spec to from the configuration of an Image
func WithImageConfig(image Image) SpecOpts {
return WithImageConfigArgs(image, nil)
}
// WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that
// replaces the CMD of the image
func WithImageConfigArgs(image Image, args []string) SpecOpts {
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
ic, err := image.Config(ctx)
if err != nil {
return err
}
var (
ociimage v1.Image
config v1.ImageConfig
)
switch ic.MediaType {
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
if err != nil {
return err
}
if err := json.Unmarshal(p, &ociimage); err != nil {
return err
}
config = ociimage.Config
default:
return fmt.Errorf("unknown image config media type %s", ic.MediaType)
}
setProcess(s)
s.Process.Env = append(s.Process.Env, config.Env...)
cmd := config.Cmd
if len(args) > 0 {
cmd = args
}
s.Process.Args = append(config.Entrypoint, cmd...)
cwd := config.WorkingDir
if cwd == "" {
cwd = "/"
}
s.Process.Cwd = cwd
if config.User != "" {
return WithUser(config.User)(ctx, client, c, s)
}
return nil
}
}
// WithRootFSPath specifies unmanaged rootfs path.
func WithRootFSPath(path string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setRoot(s)
s.Root.Path = path
// Entrypoint is not set here (it's up to caller)
return nil
}
}
// WithRootFSReadonly sets specs.Root.Readonly to true
func WithRootFSReadonly() SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setRoot(s)
s.Root.Readonly = true
return nil
}
}
// WithNoNewPrivileges sets no_new_privileges on the process for the container
func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.NoNewPrivileges = true
return nil
}
// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
s.Mounts = append(s.Mounts, specs.Mount{
Destination: "/etc/hosts",
Type: "bind",
Source: "/etc/hosts",
Options: []string{"rbind", "ro"},
})
return nil
}
// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
s.Mounts = append(s.Mounts, specs.Mount{
Destination: "/etc/resolv.conf",
Type: "bind",
Source: "/etc/resolv.conf",
Options: []string{"rbind", "ro"},
})
return nil
}
// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
s.Mounts = append(s.Mounts, specs.Mount{
Destination: "/etc/localtime",
Type: "bind",
Source: "/etc/localtime",
Options: []string{"rbind", "ro"},
})
return nil
}
// WithUserNamespace sets the uid and gid mappings for the task
// this can be called multiple times to add more mappings to the generated spec
func WithUserNamespace(container, host, size uint32) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
var hasUserns bool
setLinux(s)
for _, ns := range s.Linux.Namespaces {
if ns.Type == specs.UserNamespace {
hasUserns = true
break
}
}
if !hasUserns {
s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
Type: specs.UserNamespace,
})
}
mapping := specs.LinuxIDMapping{
ContainerID: container,
HostID: host,
Size: size,
}
s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping)
s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping)
return nil
}
}
// WithCgroup sets the container's cgroup path
func WithCgroup(path string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
s.Linux.CgroupsPath = path
return nil
}
}
// WithNamespacedCgroup uses the namespace set on the context to create a
// root directory for containers in the cgroup with the id as the subcgroup
func WithNamespacedCgroup() SpecOpts {
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return err
}
setLinux(s)
s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
return nil
}
}
// WithUser sets the user to be used within the container.
// It accepts a valid user string in OCI Image Spec v1.0.0:
// user, uid, user:group, uid:gid, uid:group, user:gid
func WithUser(userstr string) SpecOpts {
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
setProcess(s)
parts := strings.Split(userstr, ":")
switch len(parts) {
case 1:
v, err := strconv.Atoi(parts[0])
if err != nil {
// if we cannot parse as a uint they try to see if it is a username
return WithUsername(userstr)(ctx, client, c, s)
}
return WithUserID(uint32(v))(ctx, client, c, s)
case 2:
var (
username string
groupname string
)
var uid, gid uint32
v, err := strconv.Atoi(parts[0])
if err != nil {
username = parts[0]
} else {
uid = uint32(v)
}
if v, err = strconv.Atoi(parts[1]); err != nil {
groupname = parts[1]
} else {
gid = uint32(v)
}
if username == "" && groupname == "" {
s.Process.User.UID, s.Process.User.GID = uid, gid
return nil
}
f := func(root string) error {
if username != "" {
uid, _, err = getUIDGIDFromPath(root, func(u user.User) bool {
return u.Name == username
})
if err != nil {
return err
}
}
if groupname != "" {
gid, err = getGIDFromPath(root, func(g user.Group) bool {
return g.Name == groupname
})
if err != nil {
return err
}
}
s.Process.User.UID, s.Process.User.GID = uid, gid
return nil
}
if c.Snapshotter == "" && c.SnapshotKey == "" {
if !isRootfsAbs(s.Root.Path) {
return errors.New("rootfs absolute path is required")
}
return f(s.Root.Path)
}
if c.Snapshotter == "" {
return errors.New("no snapshotter set for container")
}
if c.SnapshotKey == "" {
return errors.New("rootfs snapshot not created for container")
}
snapshotter := client.SnapshotService(c.Snapshotter)
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
if err != nil {
return err
}
return mount.WithTempMount(ctx, mounts, f)
default:
return fmt.Errorf("invalid USER value %s", userstr)
}
}
}
// WithUIDGID allows the UID and GID for the Process to be set
func WithUIDGID(uid, gid uint32) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.User.UID = uid
s.Process.User.GID = gid
return nil
}
}
// WithUserID sets the correct UID and GID for the container based
// on the image's /etc/passwd contents. If /etc/passwd does not exist,
// or uid is not found in /etc/passwd, it sets the requested uid,
// additionally sets the gid to 0, and does not return an error.
func WithUserID(uid uint32) SpecOpts {
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
setProcess(s)
if c.Snapshotter == "" && c.SnapshotKey == "" {
if !isRootfsAbs(s.Root.Path) {
return errors.Errorf("rootfs absolute path is required")
}
uuid, ugid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
return u.Uid == int(uid)
})
if err != nil {
if os.IsNotExist(err) || err == errNoUsersFound {
s.Process.User.UID, s.Process.User.GID = uid, 0
return nil
}
return err
}
s.Process.User.UID, s.Process.User.GID = uuid, ugid
return nil
}
if c.Snapshotter == "" {
return errors.Errorf("no snapshotter set for container")
}
if c.SnapshotKey == "" {
return errors.Errorf("rootfs snapshot not created for container")
}
snapshotter := client.SnapshotService(c.Snapshotter)
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
if err != nil {
return err
}
return mount.WithTempMount(ctx, mounts, func(root string) error {
uuid, ugid, err := getUIDGIDFromPath(root, func(u user.User) bool {
return u.Uid == int(uid)
})
if err != nil {
if os.IsNotExist(err) || err == errNoUsersFound {
s.Process.User.UID, s.Process.User.GID = uid, 0
return nil
}
return err
}
s.Process.User.UID, s.Process.User.GID = uuid, ugid
return nil
})
}
}
// WithUsername sets the correct UID and GID for the container
// based on the the image's /etc/passwd contents. If /etc/passwd
// does not exist, or the username is not found in /etc/passwd,
// it returns error.
func WithUsername(username string) SpecOpts {
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
setProcess(s)
if c.Snapshotter == "" && c.SnapshotKey == "" {
if !isRootfsAbs(s.Root.Path) {
return errors.Errorf("rootfs absolute path is required")
}
uid, gid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
return u.Name == username
})
if err != nil {
return err
}
s.Process.User.UID, s.Process.User.GID = uid, gid
return nil
}
if c.Snapshotter == "" {
return errors.Errorf("no snapshotter set for container")
}
if c.SnapshotKey == "" {
return errors.Errorf("rootfs snapshot not created for container")
}
snapshotter := client.SnapshotService(c.Snapshotter)
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
if err != nil {
return err
}
return mount.WithTempMount(ctx, mounts, func(root string) error {
uid, gid, err := getUIDGIDFromPath(root, func(u user.User) bool {
return u.Name == username
})
if err != nil {
return err
}
s.Process.User.UID, s.Process.User.GID = uid, gid
return nil
})
}
}
// WithCapabilities sets Linux capabilities on the process
func WithCapabilities(caps []string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setCapabilities(s)
s.Process.Capabilities.Bounding = caps
s.Process.Capabilities.Effective = caps
s.Process.Capabilities.Permitted = caps
s.Process.Capabilities.Inheritable = caps
return nil
}
}
// WithAllCapabilities sets all linux capabilities for the process
var WithAllCapabilities = WithCapabilities(getAllCapabilities())
func getAllCapabilities() []string {
last := capability.CAP_LAST_CAP
// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
if last == capability.Cap(63) {
last = capability.CAP_BLOCK_SUSPEND
}
var caps []string
for _, cap := range capability.List() {
if cap > last {
continue
}
caps = append(caps, "CAP_"+strings.ToUpper(cap.String()))
}
return caps
}
// WithAmbientCapabilities set the Linux ambient capabilities for the process
// Ambient capabilities should only be set for non-root users or the caller should
// understand how these capabilities are used and set
func WithAmbientCapabilities(caps []string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setCapabilities(s)
s.Process.Capabilities.Ambient = caps
return nil
}
}
var errNoUsersFound = errors.New("no users found")
func getUIDGIDFromPath(root string, filter func(user.User) bool) (uid, gid uint32, err error) {
ppath, err := fs.RootPath(root, "/etc/passwd")
if err != nil {
return 0, 0, err
}
users, err := user.ParsePasswdFileFilter(ppath, filter)
if err != nil {
return 0, 0, err
}
if len(users) == 0 {
return 0, 0, errNoUsersFound
}
u := users[0]
return uint32(u.Uid), uint32(u.Gid), nil
}
var errNoGroupsFound = errors.New("no groups found")
func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
gpath, err := fs.RootPath(root, "/etc/group")
if err != nil {
return 0, err
}
groups, err := user.ParseGroupFileFilter(gpath, filter)
if err != nil {
return 0, err
}
if len(groups) == 0 {
return 0, errNoGroupsFound
}
g := groups[0]
return uint32(g.Gid), nil
}
func isRootfsAbs(root string) bool {
return filepath.IsAbs(root)
}
// WithMaskedPaths sets the masked paths option
func WithMaskedPaths(paths []string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
s.Linux.MaskedPaths = paths
return nil
}
}
// WithReadonlyPaths sets the read only paths option
func WithReadonlyPaths(paths []string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
s.Linux.ReadonlyPaths = paths
return nil
}
}
// WithWriteableSysfs makes any sysfs mounts writeable
func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
for i, m := range s.Mounts {
if m.Type == "sysfs" {
var options []string
for _, o := range m.Options {
if o == "ro" {
o = "rw"
}
options = append(options, o)
}
s.Mounts[i].Options = options
}
}
return nil
}
// WithWriteableCgroupfs makes any cgroup mounts writeable
func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
for i, m := range s.Mounts {
if m.Type == "cgroup" {
var options []string
for _, o := range m.Options {
if o == "ro" {
o = "rw"
}
options = append(options, o)
}
s.Mounts[i].Options = options
}
}
return nil
}
// WithSelinuxLabel sets the process SELinux label
func WithSelinuxLabel(label string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.SelinuxLabel = label
return nil
}
}
// WithApparmorProfile sets the Apparmor profile for the process
func WithApparmorProfile(profile string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.ApparmorProfile = profile
return nil
}
}
// WithSeccompUnconfined clears the seccomp profile
func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
s.Linux.Seccomp = nil
return nil
}
// WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's
// allowed and denied devices
func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
if s.Linux.Resources == nil {
s.Linux.Resources = &specs.LinuxResources{}
}
s.Linux.Resources.Devices = nil
return nil
}
// WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to
// the container's resource cgroup spec
func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
if s.Linux.Resources == nil {
s.Linux.Resources = &specs.LinuxResources{}
}
intptr := func(i int64) *int64 {
return &i
}
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{
{
// "/dev/null",
Type: "c",
Major: intptr(1),
Minor: intptr(3),
Access: rwm,
Allow: true,
},
{
// "/dev/random",
Type: "c",
Major: intptr(1),
Minor: intptr(8),
Access: rwm,
Allow: true,
},
{
// "/dev/full",
Type: "c",
Major: intptr(1),
Minor: intptr(7),
Access: rwm,
Allow: true,
},
{
// "/dev/tty",
Type: "c",
Major: intptr(5),
Minor: intptr(0),
Access: rwm,
Allow: true,
},
{
// "/dev/zero",
Type: "c",
Major: intptr(1),
Minor: intptr(5),
Access: rwm,
Allow: true,
},
{
// "/dev/urandom",
Type: "c",
Major: intptr(1),
Minor: intptr(9),
Access: rwm,
Allow: true,
},
{
// "/dev/console",
Type: "c",
Major: intptr(5),
Minor: intptr(1),
Access: rwm,
Allow: true,
},
// /dev/pts/ - pts namespaces are "coming soon"
{
Type: "c",
Major: intptr(136),
Access: rwm,
Allow: true,
},
{
Type: "c",
Major: intptr(5),
Minor: intptr(2),
Access: rwm,
Allow: true,
},
{
// tuntap
Type: "c",
Major: intptr(10),
Minor: intptr(200),
Access: rwm,
Allow: true,
},
}...)
return nil
}
// WithPrivileged sets up options for a privileged container
// TODO(justincormack) device handling
var WithPrivileged = Compose(
WithAllCapabilities,
WithMaskedPaths(nil),
WithReadonlyPaths(nil),
WithWriteableSysfs,
WithWriteableCgroupfs,
WithSelinuxLabel(""),
WithApparmorProfile(""),
WithSeccompUnconfined,
)

View file

@ -1,89 +0,0 @@
// +build windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package oci
import (
"context"
"encoding/json"
"fmt"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
// WithImageConfig configures the spec to from the configuration of an Image
func WithImageConfig(image Image) SpecOpts {
return func(ctx context.Context, client Client, _ *containers.Container, s *Spec) error {
setProcess(s)
ic, err := image.Config(ctx)
if err != nil {
return err
}
var (
ociimage v1.Image
config v1.ImageConfig
)
switch ic.MediaType {
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
if err != nil {
return err
}
if err := json.Unmarshal(p, &ociimage); err != nil {
return err
}
config = ociimage.Config
default:
return fmt.Errorf("unknown image config media type %s", ic.MediaType)
}
s.Process.Env = config.Env
s.Process.Args = append(config.Entrypoint, config.Cmd...)
s.Process.User = specs.User{
Username: config.User,
}
return nil
}
}
// WithTTY sets the information on the spec as well as the environment variables for
// using a TTY
func WithTTY(width, height int) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setProcess(s)
s.Process.Terminal = true
if s.Process.ConsoleSize == nil {
s.Process.ConsoleSize = &specs.Box{}
}
s.Process.ConsoleSize.Width = uint(width)
s.Process.ConsoleSize.Height = uint(height)
return nil
}
}
// WithUsername sets the username on the process
func WithUsername(username string) SpecOpts {
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
setProcess(s)
s.Process.User.Username = username
return nil
}
}

View file

@ -1,188 +0,0 @@
// +build !windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package oci
import (
"context"
"path/filepath"
"github.com/containerd/containerd/namespaces"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
const (
rwm = "rwm"
defaultRootfsPath = "rootfs"
)
var (
defaultEnv = []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
}
)
func defaultCaps() []string {
return []string{
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE",
}
}
func defaultNamespaces() []specs.LinuxNamespace {
return []specs.LinuxNamespace{
{
Type: specs.PIDNamespace,
},
{
Type: specs.IPCNamespace,
},
{
Type: specs.UTSNamespace,
},
{
Type: specs.MountNamespace,
},
{
Type: specs.NetworkNamespace,
},
}
}
func populateDefaultSpec(ctx context.Context, s *Spec, id string) error {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return err
}
*s = Spec{
Version: specs.Version,
Root: &specs.Root{
Path: defaultRootfsPath,
},
Process: &specs.Process{
Env: defaultEnv,
Cwd: "/",
NoNewPrivileges: true,
User: specs.User{
UID: 0,
GID: 0,
},
Capabilities: &specs.LinuxCapabilities{
Bounding: defaultCaps(),
Permitted: defaultCaps(),
Inheritable: defaultCaps(),
Effective: defaultCaps(),
},
Rlimits: []specs.POSIXRlimit{
{
Type: "RLIMIT_NOFILE",
Hard: uint64(1024),
Soft: uint64(1024),
},
},
},
Mounts: []specs.Mount{
{
Destination: "/proc",
Type: "proc",
Source: "proc",
},
{
Destination: "/dev",
Type: "tmpfs",
Source: "tmpfs",
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
},
{
Destination: "/dev/pts",
Type: "devpts",
Source: "devpts",
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
},
{
Destination: "/dev/shm",
Type: "tmpfs",
Source: "shm",
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
},
{
Destination: "/dev/mqueue",
Type: "mqueue",
Source: "mqueue",
Options: []string{"nosuid", "noexec", "nodev"},
},
{
Destination: "/sys",
Type: "sysfs",
Source: "sysfs",
Options: []string{"nosuid", "noexec", "nodev", "ro"},
},
{
Destination: "/run",
Type: "tmpfs",
Source: "tmpfs",
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
},
},
Linux: &specs.Linux{
MaskedPaths: []string{
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/sys/firmware",
"/proc/scsi",
},
ReadonlyPaths: []string{
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger",
},
CgroupsPath: filepath.Join("/", ns, id),
Resources: &specs.LinuxResources{
Devices: []specs.LinuxDeviceCgroup{
{
Allow: false,
Access: rwm,
},
},
},
Namespaces: defaultNamespaces(),
},
}
return nil
}

View file

@ -22,11 +22,6 @@ import (
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
// Default returns the default matcher for the platform.
func Default() MatchComparer {
return Only(DefaultSpec())
}
// DefaultString returns the default string specifier for the platform.
func DefaultString() string {
return Format(DefaultSpec())

View file

@ -1,3 +1,5 @@
// +build !windows
/*
Copyright The containerd Authors.
@ -14,31 +16,9 @@
limitations under the License.
*/
package oci
package platforms
import (
"context"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
func populateDefaultSpec(ctx context.Context, s *Spec, id string) error {
*s = Spec{
Version: specs.Version,
Root: &specs.Root{},
Process: &specs.Process{
Cwd: `C:\`,
ConsoleSize: &specs.Box{
Width: 80,
Height: 20,
},
},
Windows: &specs.Windows{
IgnoreFlushesDuringBoot: true,
Network: &specs.WindowsNetwork{
AllowUnqualifiedDNSQuery: true,
},
},
}
return nil
// Default returns the default matcher for the platform.
func Default() MatchComparer {
return Only(DefaultSpec())
}

View file

@ -1,3 +1,5 @@
// +build windows
/*
Copyright The containerd Authors.
@ -14,18 +16,16 @@
limitations under the License.
*/
package containerd
package platforms
import (
"context"
specs "github.com/opencontainers/runtime-spec/specs-go"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
// WithResources sets the provided resources on the spec for task updates
func WithResources(resources *specs.WindowsResources) UpdateTaskOpts {
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
r.Resources = resources
return nil
}
// Default returns the default matcher for the platform.
func Default() MatchComparer {
return Ordered(DefaultSpec(), specs.Platform{
OS: "linux",
Architecture: "amd64",
})
}

View file

@ -117,7 +117,7 @@ func (r dockerFetcher) open(ctx context.Context, u, mediatype string, offset int
}
} else {
// TODO: Should any cases where use of content range
// without the proper header be considerd?
// without the proper header be considered?
// 206 responses?
// Discard up to offset

View file

@ -134,7 +134,7 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
// There is an edge case here where offset == size of the content. If
// we seek, we will probably get an error for content that cannot be
// sought (?). In that case, we should err on committing the content,
// as the length is already satisified but we just return the empty
// as the length is already satisfied but we just return the empty
// reader instead.
hrs.rc = ioutil.NopCloser(bytes.NewReader([]byte{}))

View file

@ -272,8 +272,14 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
return err
}
// TODO: Check if blob -> diff id mapping already exists
// TODO: Check if blob empty label exists
reuse, err := c.reuseLabelBlobState(ctx, desc)
if err != nil {
return err
}
if reuse {
return nil
}
ra, err := c.contentStore.ReaderAt(ctx, desc)
if err != nil {
@ -343,6 +349,17 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
state := calc.State()
cinfo := content.Info{
Digest: desc.Digest,
Labels: map[string]string{
"containerd.io/uncompressed": state.diffID.String(),
},
}
if _, err := c.contentStore.Update(ctx, cinfo, "labels.containerd.io/uncompressed"); err != nil {
return errors.Wrap(err, "failed to update uncompressed label")
}
c.mu.Lock()
c.blobMap[desc.Digest] = state
c.layerBlobs[state.diffID] = desc
@ -351,6 +368,40 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
return nil
}
func (c *Converter) reuseLabelBlobState(ctx context.Context, desc ocispec.Descriptor) (bool, error) {
cinfo, err := c.contentStore.Info(ctx, desc.Digest)
if err != nil {
return false, errors.Wrap(err, "failed to get blob info")
}
desc.Size = cinfo.Size
diffID, ok := cinfo.Labels["containerd.io/uncompressed"]
if !ok {
return false, nil
}
bState := blobState{empty: false}
if bState.diffID, err = digest.Parse(diffID); err != nil {
log.G(ctx).WithField("id", desc.Digest).Warnf("failed to parse digest from label containerd.io/uncompressed: %v", diffID)
return false, nil
}
// NOTE: there is no need to read header to get compression method
// because there are only two kinds of methods.
if bState.diffID == desc.Digest {
desc.MediaType = images.MediaTypeDockerSchema2Layer
} else {
desc.MediaType = images.MediaTypeDockerSchema2LayerGzip
}
c.mu.Lock()
c.blobMap[desc.Digest] = bState
c.layerBlobs[bState.diffID] = desc
c.mu.Unlock()
return true, nil
}
func (c *Converter) schema1ManifestHistory() ([]ocispec.History, []digest.Digest, error) {
if c.pulledManifest == nil {
return nil, nil, errors.New("missing schema 1 manifest for conversion")

View file

@ -147,7 +147,7 @@ func (e *execProcess) start(ctx context.Context) (err error) {
return errors.Wrap(err, "creating new NULL IO")
}
} else {
if e.io, err = runc.NewPipeIO(e.parent.IoUID, e.parent.IoGID); err != nil {
if e.io, err = runc.NewPipeIO(e.parent.IoUID, e.parent.IoGID, withConditionalIO(e.stdio)); err != nil {
return errors.Wrap(err, "failed to create runc io pipes")
}
}

View file

@ -60,11 +60,11 @@ func (s *execCreatedState) Start(ctx context.Context) error {
}
func (s *execCreatedState) Delete(ctx context.Context) error {
s.p.mu.Lock()
defer s.p.mu.Unlock()
if err := s.p.delete(ctx); err != nil {
return err
}
s.p.mu.Lock()
defer s.p.mu.Unlock()
return s.transition("deleted")
}
@ -168,11 +168,11 @@ func (s *execStoppedState) Start(ctx context.Context) error {
}
func (s *execStoppedState) Delete(ctx context.Context) error {
s.p.mu.Lock()
defer s.p.mu.Unlock()
if err := s.p.delete(ctx); err != nil {
return err
}
s.p.mu.Lock()
defer s.p.mu.Unlock()
return s.transition("deleted")
}

View file

@ -123,7 +123,7 @@ func (p *Init) Create(ctx context.Context, r *CreateConfig) error {
return errors.Wrap(err, "creating new NULL IO")
}
} else {
if p.io, err = runc.NewPipeIO(p.IoUID, p.IoGID); err != nil {
if p.io, err = runc.NewPipeIO(p.IoUID, p.IoGID, withConditionalIO(p.stdio)); err != nil {
return errors.Wrap(err, "failed to create OCI runtime io pipes")
}
}
@ -228,7 +228,7 @@ func (p *Init) Status(ctx context.Context) (string, error) {
defer p.mu.Unlock()
c, err := p.runtime.State(ctx, p.id)
if err != nil {
if os.IsNotExist(err) {
if strings.Contains(err.Error(), "does not exist") {
return "stopped", nil
}
return "", p.runtimeError(err, "OCI runtime state failed")
@ -249,7 +249,6 @@ func (p *Init) setExited(status int) {
}
func (p *Init) delete(context context.Context) error {
p.KillAll(context)
p.wg.Wait()
err := p.runtime.Delete(context, p.id, nil)
// ignore errors if a runtime has already deleted the process
@ -400,3 +399,11 @@ func (p *Init) runtimeError(rErr error, msg string) error {
return errors.Errorf("%s: %s", msg, rMsg)
}
}
func withConditionalIO(c proc.Stdio) runc.IOOpt {
return func(o *runc.IOOption) {
o.OpenStdin = c.Stdin != ""
o.OpenStdout = c.Stdout != ""
o.OpenStderr = c.Stderr != ""
}
}

View file

@ -109,7 +109,6 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w
i.dest(fw, fr)
}
if stdin == "" {
rio.Stdin().Close()
return nil
}
f, err := fifo.OpenFifo(ctx, stdin, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)

View file

@ -26,7 +26,6 @@ import (
"path/filepath"
"time"
"github.com/boltdb/bolt"
eventstypes "github.com/containerd/containerd/api/events"
"github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/containers"
@ -49,6 +48,7 @@ import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
bolt "go.etcd.io/bbolt"
"golang.org/x/sys/unix"
)
@ -204,7 +204,7 @@ func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts
log.G(ctx).WithError(err).WithFields(logrus.Fields{
"id": id,
"namespace": namespace,
}).Warn("failed to clen up after killed shim")
}).Warn("failed to clean up after killed shim")
}
}
shimopt = ShimRemote(r.config, r.address, cgroup, exitHandler)
@ -248,8 +248,7 @@ func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts
if err != nil {
return nil, errdefs.FromGRPC(err)
}
t, err := newTask(id, namespace, int(cr.Pid), s, r.events,
proc.NewRunc(ropts.RuntimeRoot, sopts.Bundle, namespace, rt, ropts.CriuPath, ropts.SystemdCgroup), r.tasks, bundle)
t, err := newTask(id, namespace, int(cr.Pid), s, r.events, r.tasks, bundle)
if err != nil {
return nil, err
}
@ -341,15 +340,8 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
}
continue
}
ropts, err := r.getRuncOptions(ctx, id)
if err != nil {
log.G(ctx).WithError(err).WithField("id", id).
Error("get runtime options")
continue
}
t, err := newTask(id, ns, pid, s, r.events,
proc.NewRunc(ropts.RuntimeRoot, bundle.path, ns, ropts.Runtime, ropts.CriuPath, ropts.SystemdCgroup), r.tasks, bundle)
t, err := newTask(id, ns, pid, s, r.events, r.tasks, bundle)
if err != nil {
log.G(ctx).WithError(err).Error("loading task type")
continue

View file

@ -31,8 +31,7 @@ import (
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/runtime"
"github.com/containerd/containerd/runtime/v1/shim/client"
shim "github.com/containerd/containerd/runtime/v1/shim/v1"
runc "github.com/containerd/go-runc"
"github.com/containerd/containerd/runtime/v1/shim/v1"
"github.com/containerd/ttrpc"
"github.com/containerd/typeurl"
"github.com/gogo/protobuf/types"
@ -52,7 +51,7 @@ type Task struct {
bundle *bundle
}
func newTask(id, namespace string, pid int, shim *client.Client, events *exchange.Exchange, runtime *runc.Runc, list *runtime.TaskList, bundle *bundle) (*Task, error) {
func newTask(id, namespace string, pid int, shim *client.Client, events *exchange.Exchange, list *runtime.TaskList, bundle *bundle) (*Task, error) {
var (
err error
cg cgroups.Cgroup

View file

@ -20,7 +20,9 @@ package shim
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
@ -41,6 +43,7 @@ import (
runc "github.com/containerd/go-runc"
"github.com/containerd/typeurl"
ptypes "github.com/gogo/protobuf/types"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
@ -221,19 +224,21 @@ func (s *Service) Delete(ctx context.Context, r *ptypes.Empty) (*shimapi.DeleteR
// DeleteProcess deletes an exec'd process
func (s *Service) DeleteProcess(ctx context.Context, r *shimapi.DeleteProcessRequest) (*shimapi.DeleteResponse, error) {
s.mu.Lock()
defer s.mu.Unlock()
if r.ID == s.id {
return nil, status.Errorf(codes.InvalidArgument, "cannot delete init process with DeleteProcess")
}
s.mu.Lock()
p := s.processes[r.ID]
s.mu.Unlock()
if p == nil {
return nil, errors.Wrapf(errdefs.ErrNotFound, "process %s", r.ID)
}
if err := p.Delete(ctx); err != nil {
return nil, err
}
s.mu.Lock()
delete(s.processes, r.ID)
s.mu.Unlock()
return &shimapi.DeleteResponse{
ExitStatus: uint32(p.ExitStatus()),
ExitedAt: p.ExitedAt(),
@ -507,13 +512,22 @@ func (s *Service) processExits() {
func (s *Service) checkProcesses(e runc.Exit) {
s.mu.Lock()
defer s.mu.Unlock()
shouldKillAll, err := shouldKillAllOnExit(s.bundle)
if err != nil {
log.G(s.context).WithError(err).Error("failed to check shouldKillAll")
}
for _, p := range s.processes {
if p.Pid() == e.Pid {
if ip, ok := p.(*proc.Init); ok {
// Ensure all children are killed
if err := ip.KillAll(s.context); err != nil {
log.G(s.context).WithError(err).WithField("id", ip.ID()).
Error("failed to kill init's children")
if shouldKillAll {
if ip, ok := p.(*proc.Init); ok {
// Ensure all children are killed
if err := ip.KillAll(s.context); err != nil {
log.G(s.context).WithError(err).WithField("id", ip.ID()).
Error("failed to kill init's children")
}
}
}
p.SetExited(e.Status)
@ -529,6 +543,25 @@ func (s *Service) checkProcesses(e runc.Exit) {
}
}
func shouldKillAllOnExit(bundlePath string) (bool, error) {
var bundleSpec specs.Spec
bundleConfigContents, err := ioutil.ReadFile(filepath.Join(bundlePath, "config.json"))
if err != nil {
return false, err
}
json.Unmarshal(bundleConfigContents, &bundleSpec)
if bundleSpec.Linux != nil {
for _, ns := range bundleSpec.Linux.Namespaces {
if ns.Type == specs.PIDNamespace {
return false, nil
}
}
}
return true, nil
}
func (s *Service) getContainerPids(ctx context.Context, id string) ([]uint32, error) {
s.mu.Lock()
defer s.mu.Unlock()

View file

@ -29,7 +29,6 @@ import (
"sync"
"time"
"github.com/boltdb/bolt"
csapi "github.com/containerd/containerd/api/services/content/v1"
ssapi "github.com/containerd/containerd/api/services/snapshots/v1"
"github.com/containerd/containerd/content"
@ -46,6 +45,7 @@ import (
metrics "github.com/docker/go-metrics"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
"google.golang.org/grpc"
)

View file

@ -42,7 +42,7 @@ func CreateUnixSocket(path string) (net.Listener, error) {
return net.Listen("unix", path)
}
// GetLocalListener returns a listerner out of a unix socket.
// GetLocalListener returns a listener out of a unix socket.
func GetLocalListener(path string, uid, gid int) (net.Listener, error) {
// Ensure parent directory is created
if err := mkdirAs(filepath.Dir(path), uid, gid); err != nil {

View file

@ -607,8 +607,11 @@ func writeContent(ctx context.Context, store content.Ingester, mediaType, ref st
if err != nil {
return d, err
}
if err := writer.Commit(ctx, size, "", opts...); err != nil {
return d, err
if !errdefs.IsAlreadyExists(err) {
return d, err
}
}
return v1.Descriptor{
MediaType: mediaType,

View file

@ -18,10 +18,18 @@ package containerd
import (
"context"
"encoding/json"
"fmt"
"syscall"
"github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/mount"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
// NewTaskOpts allows the caller to set options on a new task
@ -35,6 +43,44 @@ func WithRootFS(mounts []mount.Mount) NewTaskOpts {
}
}
// WithTaskCheckpoint allows a task to be created with live runtime and memory data from a
// previous checkpoint. Additional software such as CRIU may be required to
// restore a task from a checkpoint
func WithTaskCheckpoint(im Image) NewTaskOpts {
return func(ctx context.Context, c *Client, info *TaskInfo) error {
desc := im.Target()
id := desc.Digest
index, err := decodeIndex(ctx, c.ContentStore(), desc)
if err != nil {
return err
}
for _, m := range index.Manifests {
if m.MediaType == images.MediaTypeContainerd1Checkpoint {
info.Checkpoint = &types.Descriptor{
MediaType: m.MediaType,
Size_: m.Size,
Digest: m.Digest,
}
return nil
}
}
return fmt.Errorf("checkpoint not found in index %s", id)
}
}
func decodeIndex(ctx context.Context, store content.Provider, desc imagespec.Descriptor) (*imagespec.Index, error) {
var index imagespec.Index
p, err := content.ReadBlob(ctx, store, desc)
if err != nil {
return nil, err
}
if err := json.Unmarshal(p, &index); err != nil {
return nil, err
}
return &index, nil
}
// WithCheckpointName sets the image name for the checkpoint
func WithCheckpointName(name string) CheckpointTaskOpts {
return func(r *CheckpointTaskInfo) error {
@ -92,3 +138,19 @@ func WithKillExecID(execID string) KillOpts {
return nil
}
}
// WithResources sets the provided resources for task updates. Resources must be
// either a *specs.LinuxResources or a *specs.WindowsResources
func WithResources(resources interface{}) UpdateTaskOpts {
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
switch resources.(type) {
case *specs.LinuxResources:
case *specs.WindowsResources:
default:
return errors.New("WithResources requires a *specs.LinuxResources or *specs.WindowsResources")
}
r.Resources = resources
return nil
}
}

View file

@ -1,3 +1,5 @@
// +build !windows
/*
Copyright The containerd Authors.
@ -18,20 +20,11 @@ package containerd
import (
"context"
"errors"
"github.com/containerd/containerd/runtime/linux/runctypes"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
// WithResources sets the provided resources for task updates
func WithResources(resources *specs.LinuxResources) UpdateTaskOpts {
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
r.Resources = resources
return nil
}
}
// WithNoNewKeyring causes tasks not to be created with a new keyring for secret storage.
// There is an upper limit on the number of keyrings in a linux system
func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error {
@ -46,3 +39,19 @@ func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error {
opts.NoNewKeyring = true
return nil
}
// WithNoPivotRoot instructs the runtime not to you pivot_root
func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
if info.Options == nil {
info.Options = &runctypes.CreateOptions{
NoPivotRoot: true,
}
return nil
}
opts, ok := info.Options.(*runctypes.CreateOptions)
if !ok {
return errors.New("invalid options type, expected runctypes.CreateOptions")
}
opts.NoPivotRoot = true
return nil
}

View file

@ -1,10 +1,10 @@
github.com/containerd/go-runc acb7c88cac264acca9b5eae187a117f4d77a1292
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244
github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b
github.com/containerd/continuity f44b615e492bdfb371aae2f76ec694d9da1db537
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
@ -19,7 +19,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.0
github.com/gogo/protobuf v1.0.0
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
github.com/golang/protobuf v1.1.0
github.com/opencontainers/runtime-spec d810dbc60d8c5aeeb3d054bd1132fab2121968ce # v1.0.1-43-gd810dbc
github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 # v1.0.1-45-geba862d
github.com/opencontainers/runc 20aff4f0488c6d4b8df4d85b4f63f1f704c11abd
github.com/sirupsen/logrus v1.0.0
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
@ -34,17 +34,17 @@ github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
github.com/Microsoft/go-winio v0.4.10
github.com/Microsoft/hcsshim 44c060121b68e8bdc40b411beba551f3b4ee9e55
github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
github.com/containerd/ttrpc 94dde388801693c54f88a6596f713b51a8b30b2d
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
gotest.tools v2.1.0
github.com/google/go-cmp v0.1.0
go.etcd.io/bbolt v1.3.1-etcd.8
# cri dependencies
github.com/containerd/cri v1.11.1
github.com/containerd/go-cni 5882530828ecf62032409b298a3e8b19e08b6534
github.com/containerd/cri 9f39e3289533fc228c5e5fcac0a6dbdd60c6047b # release/1.2 branch
github.com/containerd/go-cni 6d7b509a054a3cb1c35ed1865d4fde2f0cb547cd
github.com/blang/semver v3.1.0
github.com/containernetworking/cni v0.6.0
github.com/containernetworking/plugins v0.7.0
@ -52,32 +52,33 @@ github.com/davecgh/go-spew v1.1.0
github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621
github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00
github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528
github.com/emicklei/go-restful ff4f55a206334ef123e4f79bbf348980da81ca46
github.com/ghodss/yaml 73d445a93680fa1a78ae23a5839bad48f32ba1ee
github.com/emicklei/go-restful v2.2.1
github.com/ghodss/yaml v1.0.0
github.com/golang/glog 44145f04b68cf362d9c4df2182967c2275eaefed
github.com/google/gofuzz 44d81051d367757e1c7c6a5a86423ece9afcf63c
github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55
github.com/hashicorp/go-multierror ed905158d87462226a13fe39ddf685ea65f1c11f
github.com/json-iterator/go f2b4162afba35581b6d4a50d3b8f34e33c144682
github.com/modern-go/reflect2 05fbef0ca5da472bbf96c9322b84a53edc03c9fd
github.com/json-iterator/go 1.1.5
github.com/modern-go/reflect2 1.0.1
github.com/modern-go/concurrent 1.0.3
github.com/opencontainers/runtime-tools v0.6.0
github.com/opencontainers/selinux 4a2974bf1ee960774ffd517717f1f45325af0206
github.com/opencontainers/selinux b6fa367ed7f534f9ba25391cc2d467085dbb445a
github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0
github.com/tchap/go-patricia 5ad6cdb7538b0097d5598c7e57f0a24072adf7dc
github.com/tchap/go-patricia v2.2.6
github.com/xeipuuv/gojsonpointer 4e3ac2762d5f479393488629ee9370b50873b3a6
github.com/xeipuuv/gojsonreference bd5ef7bd5415a7ac448318e64f11a24cd21e594b
github.com/xeipuuv/gojsonschema 1d523034197ff1f222f6429836dd36a2457a1874
golang.org/x/crypto 49796115aa4b964c318aad4f3084fdb41e9aa067
golang.org/x/oauth2 a6bd8cefa1811bd24b86f8902872e4e8225f74c4
golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
gopkg.in/yaml.v2 53feefa2559fb8dfa8d81baad31be332c97d6c77
k8s.io/api 9e5ffd1f1320950b238cfce291b926411f0af722
k8s.io/apimachinery ed135c5b96450fd24e5e981c708114fbbd950697
k8s.io/apiserver a90e3a95c2e91b944bfca8225c4e0d12e42a9eb5
k8s.io/client-go 03bfb9bdcfe5482795b999f39ca3ed9ad42ce5bb
k8s.io/kubernetes v1.11.0
k8s.io/utils 733eca437aa39379e4bcc25e726439dfca40fcff
gopkg.in/yaml.v2 v2.2.1
k8s.io/api 012f271b5d41baad56190c5f1ae19bff16df0fd8
k8s.io/apimachinery 6429050ef506887d121f3e7306e894f8900d8a63
k8s.io/apiserver e9312c15296b6c2c923ebd5031ff5d1d5fd022d7
k8s.io/client-go 37c3c02ec96533daec0dbda1f39a6b1d68505c79
k8s.io/kubernetes v1.12.0-beta.1
k8s.io/utils 982821ea41da7e7c15f3d3738921eb2e7e241ccd
# zfs dependencies
github.com/containerd/zfs 9a0b8b8b5982014b729cd34eb7cd7a11062aa6ec
@ -85,4 +86,4 @@ github.com/mistifyio/go-zfs 166add352731e515512690329794ee593f1aaff2
github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd
# aufs dependencies
github.com/containerd/aufs a7fbd554da7a9eafbe5a460a421313a9fd18d988
github.com/containerd/aufs ffa39970e26ad01d81f540b21e65f9c1841a5f92

657
vendor/github.com/containerd/continuity/context.go generated vendored Normal file
View file

@ -0,0 +1,657 @@
package continuity
import (
"bytes"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"github.com/containerd/continuity/devices"
driverpkg "github.com/containerd/continuity/driver"
"github.com/containerd/continuity/pathdriver"
"github.com/opencontainers/go-digest"
)
var (
// ErrNotFound represents the resource not found
ErrNotFound = fmt.Errorf("not found")
// ErrNotSupported represents the resource not supported
ErrNotSupported = fmt.Errorf("not supported")
)
// Context represents a file system context for accessing resources. The
// responsibility of the context is to convert system specific resources to
// generic Resource objects. Most of this is safe path manipulation, as well
// as extraction of resource details.
type Context interface {
Apply(Resource) error
Verify(Resource) error
Resource(string, os.FileInfo) (Resource, error)
Walk(filepath.WalkFunc) error
}
// SymlinkPath is intended to give the symlink target value
// in a root context. Target and linkname are absolute paths
// not under the given root.
type SymlinkPath func(root, linkname, target string) (string, error)
// ContextOptions represents options to create a new context.
type ContextOptions struct {
Digester Digester
Driver driverpkg.Driver
PathDriver pathdriver.PathDriver
Provider ContentProvider
}
// context represents a file system context for accessing resources.
// Generally, all path qualified access and system considerations should land
// here.
type context struct {
driver driverpkg.Driver
pathDriver pathdriver.PathDriver
root string
digester Digester
provider ContentProvider
}
// NewContext returns a Context associated with root. The default driver will
// be used, as returned by NewDriver.
func NewContext(root string) (Context, error) {
return NewContextWithOptions(root, ContextOptions{})
}
// NewContextWithOptions returns a Context associate with the root.
func NewContextWithOptions(root string, options ContextOptions) (Context, error) {
// normalize to absolute path
pathDriver := options.PathDriver
if pathDriver == nil {
pathDriver = pathdriver.LocalPathDriver
}
root = pathDriver.FromSlash(root)
root, err := pathDriver.Abs(pathDriver.Clean(root))
if err != nil {
return nil, err
}
driver := options.Driver
if driver == nil {
driver, err = driverpkg.NewSystemDriver()
if err != nil {
return nil, err
}
}
digester := options.Digester
if digester == nil {
digester = simpleDigester{digest.Canonical}
}
// Check the root directory. Need to be a little careful here. We are
// allowing a link for now, but this may have odd behavior when
// canonicalizing paths. As long as all files are opened through the link
// path, this should be okay.
fi, err := driver.Stat(root)
if err != nil {
return nil, err
}
if !fi.IsDir() {
return nil, &os.PathError{Op: "NewContext", Path: root, Err: os.ErrInvalid}
}
return &context{
root: root,
driver: driver,
pathDriver: pathDriver,
digester: digester,
provider: options.Provider,
}, nil
}
// Resource returns the resource as path p, populating the entry with info
// from fi. The path p should be the path of the resource in the context,
// typically obtained through Walk or from the value of Resource.Path(). If fi
// is nil, it will be resolved.
func (c *context) Resource(p string, fi os.FileInfo) (Resource, error) {
fp, err := c.fullpath(p)
if err != nil {
return nil, err
}
if fi == nil {
fi, err = c.driver.Lstat(fp)
if err != nil {
return nil, err
}
}
base, err := newBaseResource(p, fi)
if err != nil {
return nil, err
}
base.xattrs, err = c.resolveXAttrs(fp, fi, base)
if err == ErrNotSupported {
log.Printf("resolving xattrs on %s not supported", fp)
} else if err != nil {
return nil, err
}
// TODO(stevvooe): Handle windows alternate data streams.
if fi.Mode().IsRegular() {
dgst, err := c.digest(p)
if err != nil {
return nil, err
}
return newRegularFile(*base, base.paths, fi.Size(), dgst)
}
if fi.Mode().IsDir() {
return newDirectory(*base)
}
if fi.Mode()&os.ModeSymlink != 0 {
// We handle relative links vs absolute links by including a
// beginning slash for absolute links. Effectively, the bundle's
// root is treated as the absolute link anchor.
target, err := c.driver.Readlink(fp)
if err != nil {
return nil, err
}
return newSymLink(*base, target)
}
if fi.Mode()&os.ModeNamedPipe != 0 {
return newNamedPipe(*base, base.paths)
}
if fi.Mode()&os.ModeDevice != 0 {
deviceDriver, ok := c.driver.(driverpkg.DeviceInfoDriver)
if !ok {
log.Printf("device extraction not supported %s", fp)
return nil, ErrNotSupported
}
// character and block devices merely need to recover the
// major/minor device number.
major, minor, err := deviceDriver.DeviceInfo(fi)
if err != nil {
return nil, err
}
return newDevice(*base, base.paths, major, minor)
}
log.Printf("%q (%v) is not supported", fp, fi.Mode())
return nil, ErrNotFound
}
func (c *context) verifyMetadata(resource, target Resource) error {
if target.Mode() != resource.Mode() {
return fmt.Errorf("resource %q has incorrect mode: %v != %v", target.Path(), target.Mode(), resource.Mode())
}
if target.UID() != resource.UID() {
return fmt.Errorf("unexpected uid for %q: %v != %v", target.Path(), target.UID(), resource.GID())
}
if target.GID() != resource.GID() {
return fmt.Errorf("unexpected gid for %q: %v != %v", target.Path(), target.GID(), target.GID())
}
if xattrer, ok := resource.(XAttrer); ok {
txattrer, tok := target.(XAttrer)
if !tok {
return fmt.Errorf("resource %q has xattrs but target does not support them", resource.Path())
}
// For xattrs, only ensure that we have those defined in the resource
// and their values match. We can ignore other xattrs. In other words,
// we only verify that target has the subset defined by resource.
txattrs := txattrer.XAttrs()
for attr, value := range xattrer.XAttrs() {
tvalue, ok := txattrs[attr]
if !ok {
return fmt.Errorf("resource %q target missing xattr %q", resource.Path(), attr)
}
if !bytes.Equal(value, tvalue) {
return fmt.Errorf("xattr %q value differs for resource %q", attr, resource.Path())
}
}
}
switch r := resource.(type) {
case RegularFile:
// TODO(stevvooe): Another reason to use a record-based approach. We
// have to do another type switch to get this to work. This could be
// fixed with an Equal function, but let's study this a little more to
// be sure.
t, ok := target.(RegularFile)
if !ok {
return fmt.Errorf("resource %q target not a regular file", r.Path())
}
if t.Size() != r.Size() {
return fmt.Errorf("resource %q target has incorrect size: %v != %v", t.Path(), t.Size(), r.Size())
}
case Directory:
t, ok := target.(Directory)
if !ok {
return fmt.Errorf("resource %q target not a directory", t.Path())
}
case SymLink:
t, ok := target.(SymLink)
if !ok {
return fmt.Errorf("resource %q target not a symlink", t.Path())
}
if t.Target() != r.Target() {
return fmt.Errorf("resource %q target has mismatched target: %q != %q", t.Path(), t.Target(), r.Target())
}
case Device:
t, ok := target.(Device)
if !ok {
return fmt.Errorf("resource %q is not a device", t.Path())
}
if t.Major() != r.Major() || t.Minor() != r.Minor() {
return fmt.Errorf("resource %q has mismatched major/minor numbers: %d,%d != %d,%d", t.Path(), t.Major(), t.Minor(), r.Major(), r.Minor())
}
case NamedPipe:
t, ok := target.(NamedPipe)
if !ok {
return fmt.Errorf("resource %q is not a named pipe", t.Path())
}
default:
return fmt.Errorf("cannot verify resource: %v", resource)
}
return nil
}
// Verify the resource in the context. An error will be returned a discrepancy
// is found.
func (c *context) Verify(resource Resource) error {
fp, err := c.fullpath(resource.Path())
if err != nil {
return err
}
fi, err := c.driver.Lstat(fp)
if err != nil {
return err
}
target, err := c.Resource(resource.Path(), fi)
if err != nil {
return err
}
if target.Path() != resource.Path() {
return fmt.Errorf("resource paths do not match: %q != %q", target.Path(), resource.Path())
}
if err := c.verifyMetadata(resource, target); err != nil {
return err
}
if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
hardlinkKey, err := newHardlinkKey(fi)
if err == errNotAHardLink {
if len(h.Paths()) > 1 {
return fmt.Errorf("%q is not a hardlink to %q", h.Paths()[1], resource.Path())
}
} else if err != nil {
return err
}
for _, path := range h.Paths()[1:] {
fpLink, err := c.fullpath(path)
if err != nil {
return err
}
fiLink, err := c.driver.Lstat(fpLink)
if err != nil {
return err
}
targetLink, err := c.Resource(path, fiLink)
if err != nil {
return err
}
hardlinkKeyLink, err := newHardlinkKey(fiLink)
if err != nil {
return err
}
if hardlinkKeyLink != hardlinkKey {
return fmt.Errorf("%q is not a hardlink to %q", path, resource.Path())
}
if err := c.verifyMetadata(resource, targetLink); err != nil {
return err
}
}
}
switch r := resource.(type) {
case RegularFile:
t, ok := target.(RegularFile)
if !ok {
return fmt.Errorf("resource %q target not a regular file", r.Path())
}
// TODO(stevvooe): This may need to get a little more sophisticated
// for digest comparison. We may want to actually calculate the
// provided digests, rather than the implementations having an
// overlap.
if !digestsMatch(t.Digests(), r.Digests()) {
return fmt.Errorf("digests for resource %q do not match: %v != %v", t.Path(), t.Digests(), r.Digests())
}
}
return nil
}
func (c *context) checkoutFile(fp string, rf RegularFile) error {
if c.provider == nil {
return fmt.Errorf("no file provider")
}
var (
r io.ReadCloser
err error
)
for _, dgst := range rf.Digests() {
r, err = c.provider.Reader(dgst)
if err == nil {
break
}
}
if err != nil {
return fmt.Errorf("file content could not be provided: %v", err)
}
defer r.Close()
return atomicWriteFile(fp, r, rf.Size(), rf.Mode())
}
// Apply the resource to the contexts. An error will be returned if the
// operation fails. Depending on the resource type, the resource may be
// created. For resource that cannot be resolved, an error will be returned.
func (c *context) Apply(resource Resource) error {
fp, err := c.fullpath(resource.Path())
if err != nil {
return err
}
if !strings.HasPrefix(fp, c.root) {
return fmt.Errorf("resource %v escapes root", resource)
}
var chmod = true
fi, err := c.driver.Lstat(fp)
if err != nil {
if !os.IsNotExist(err) {
return err
}
}
switch r := resource.(type) {
case RegularFile:
if fi == nil {
if err := c.checkoutFile(fp, r); err != nil {
return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
}
chmod = false
} else {
if !fi.Mode().IsRegular() {
return fmt.Errorf("file %q should be a regular file, but is not", resource.Path())
}
if fi.Size() != r.Size() {
if err := c.checkoutFile(fp, r); err != nil {
return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
}
} else {
for _, dgst := range r.Digests() {
f, err := os.Open(fp)
if err != nil {
return fmt.Errorf("failure opening file for read %q: %v", resource.Path(), err)
}
compared, err := dgst.Algorithm().FromReader(f)
if err == nil && dgst != compared {
if err := c.checkoutFile(fp, r); err != nil {
return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
}
break
}
if err1 := f.Close(); err == nil {
err = err1
}
if err != nil {
return fmt.Errorf("error checking digest for %q: %v", resource.Path(), err)
}
}
}
}
case Directory:
if fi == nil {
if err := c.driver.Mkdir(fp, resource.Mode()); err != nil {
return err
}
} else if !fi.Mode().IsDir() {
return fmt.Errorf("%q should be a directory, but is not", resource.Path())
}
case SymLink:
var target string // only possibly set if target resource is a symlink
if fi != nil {
if fi.Mode()&os.ModeSymlink != 0 {
target, err = c.driver.Readlink(fp)
if err != nil {
return err
}
}
}
if target != r.Target() {
if fi != nil {
if err := c.driver.Remove(fp); err != nil { // RemoveAll in case of directory?
return err
}
}
if err := c.driver.Symlink(r.Target(), fp); err != nil {
return err
}
}
case Device:
if fi == nil {
if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
return err
}
} else if (fi.Mode() & os.ModeDevice) == 0 {
return fmt.Errorf("%q should be a device, but is not", resource.Path())
} else {
major, minor, err := devices.DeviceInfo(fi)
if err != nil {
return err
}
if major != r.Major() || minor != r.Minor() {
if err := c.driver.Remove(fp); err != nil {
return err
}
if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
return err
}
}
}
case NamedPipe:
if fi == nil {
if err := c.driver.Mkfifo(fp, resource.Mode()); err != nil {
return err
}
} else if (fi.Mode() & os.ModeNamedPipe) == 0 {
return fmt.Errorf("%q should be a named pipe, but is not", resource.Path())
}
}
if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
for _, path := range h.Paths() {
if path == resource.Path() {
continue
}
lp, err := c.fullpath(path)
if err != nil {
return err
}
if _, fi := c.driver.Lstat(lp); fi == nil {
c.driver.Remove(lp)
}
if err := c.driver.Link(fp, lp); err != nil {
return err
}
}
}
// Update filemode if file was not created
if chmod {
if err := c.driver.Lchmod(fp, resource.Mode()); err != nil {
return err
}
}
if err := c.driver.Lchown(fp, resource.UID(), resource.GID()); err != nil {
return err
}
if xattrer, ok := resource.(XAttrer); ok {
// For xattrs, only ensure that we have those defined in the resource
// and their values are set. We can ignore other xattrs. In other words,
// we only set xattres defined by resource but never remove.
if _, ok := resource.(SymLink); ok {
lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
if !ok {
return fmt.Errorf("unsupported symlink xattr for resource %q", resource.Path())
}
if err := lxattrDriver.LSetxattr(fp, xattrer.XAttrs()); err != nil {
return err
}
} else {
xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
if !ok {
return fmt.Errorf("unsupported xattr for resource %q", resource.Path())
}
if err := xattrDriver.Setxattr(fp, xattrer.XAttrs()); err != nil {
return err
}
}
}
return nil
}
// Walk provides a convenience function to call filepath.Walk correctly for
// the context. Otherwise identical to filepath.Walk, the path argument is
// corrected to be contained within the context.
func (c *context) Walk(fn filepath.WalkFunc) error {
root := c.root
fi, err := c.driver.Lstat(c.root)
if err == nil && fi.Mode()&os.ModeSymlink != 0 {
root, err = c.driver.Readlink(c.root)
if err != nil {
return err
}
}
return c.pathDriver.Walk(root, func(p string, fi os.FileInfo, err error) error {
contained, err := c.containWithRoot(p, root)
return fn(contained, fi, err)
})
}
// fullpath returns the system path for the resource, joined with the context
// root. The path p must be a part of the context.
func (c *context) fullpath(p string) (string, error) {
p = c.pathDriver.Join(c.root, p)
if !strings.HasPrefix(p, c.root) {
return "", fmt.Errorf("invalid context path")
}
return p, nil
}
// contain cleans and santizes the filesystem path p to be an absolute path,
// effectively relative to the context root.
func (c *context) contain(p string) (string, error) {
return c.containWithRoot(p, c.root)
}
// containWithRoot cleans and santizes the filesystem path p to be an absolute path,
// effectively relative to the passed root. Extra care should be used when calling this
// instead of contain. This is needed for Walk, as if context root is a symlink,
// it must be evaluated prior to the Walk
func (c *context) containWithRoot(p string, root string) (string, error) {
sanitized, err := c.pathDriver.Rel(root, p)
if err != nil {
return "", err
}
// ZOMBIES(stevvooe): In certain cases, we may want to remap these to a
// "containment error", so the caller can decide what to do.
return c.pathDriver.Join("/", c.pathDriver.Clean(sanitized)), nil
}
// digest returns the digest of the file at path p, relative to the root.
func (c *context) digest(p string) (digest.Digest, error) {
f, err := c.driver.Open(c.pathDriver.Join(c.root, p))
if err != nil {
return "", err
}
defer f.Close()
return c.digester.Digest(f)
}
// resolveXAttrs attempts to resolve the extended attributes for the resource
// at the path fp, which is the full path to the resource. If the resource
// cannot have xattrs, nil will be returned.
func (c *context) resolveXAttrs(fp string, fi os.FileInfo, base *resource) (map[string][]byte, error) {
if fi.Mode().IsRegular() || fi.Mode().IsDir() {
xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
if !ok {
log.Println("xattr extraction not supported")
return nil, ErrNotSupported
}
return xattrDriver.Getxattr(fp)
}
if fi.Mode()&os.ModeSymlink != 0 {
lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
if !ok {
log.Println("xattr extraction for symlinks not supported")
return nil, ErrNotSupported
}
return lxattrDriver.LGetxattr(fp)
}
return nil, nil
}

88
vendor/github.com/containerd/continuity/digests.go generated vendored Normal file
View file

@ -0,0 +1,88 @@
package continuity
import (
"fmt"
"io"
"sort"
"github.com/opencontainers/go-digest"
)
// Digester produces a digest for a given read stream
type Digester interface {
Digest(io.Reader) (digest.Digest, error)
}
// ContentProvider produces a read stream for a given digest
type ContentProvider interface {
Reader(digest.Digest) (io.ReadCloser, error)
}
type simpleDigester struct {
algorithm digest.Algorithm
}
func (sd simpleDigester) Digest(r io.Reader) (digest.Digest, error) {
digester := sd.algorithm.Digester()
if _, err := io.Copy(digester.Hash(), r); err != nil {
return "", err
}
return digester.Digest(), nil
}
// uniqifyDigests sorts and uniqifies the provided digest, ensuring that the
// digests are not repeated and no two digests with the same algorithm have
// different values. Because a stable sort is used, this has the effect of
// "zipping" digest collections from multiple resources.
func uniqifyDigests(digests ...digest.Digest) ([]digest.Digest, error) {
sort.Stable(digestSlice(digests)) // stable sort is important for the behavior here.
seen := map[digest.Digest]struct{}{}
algs := map[digest.Algorithm][]digest.Digest{} // detect different digests.
var out []digest.Digest
// uniqify the digests
for _, d := range digests {
if _, ok := seen[d]; ok {
continue
}
seen[d] = struct{}{}
algs[d.Algorithm()] = append(algs[d.Algorithm()], d)
if len(algs[d.Algorithm()]) > 1 {
return nil, fmt.Errorf("conflicting digests for %v found", d.Algorithm())
}
out = append(out, d)
}
return out, nil
}
// digestsMatch compares the two sets of digests to see if they match.
func digestsMatch(as, bs []digest.Digest) bool {
all := append(as, bs...)
uniqified, err := uniqifyDigests(all...)
if err != nil {
// the only error uniqifyDigests returns is when the digests disagree.
return false
}
disjoint := len(as) + len(bs)
if len(uniqified) == disjoint {
// if these two sets have the same cardinality, we know both sides
// didn't share any digests.
return false
}
return true
}
type digestSlice []digest.Digest
func (p digestSlice) Len() int { return len(p) }
func (p digestSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p digestSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

113
vendor/github.com/containerd/continuity/groups_unix.go generated vendored Normal file
View file

@ -0,0 +1,113 @@
package continuity
import (
"bufio"
"fmt"
"io"
"os"
"strconv"
"strings"
)
// TODO(stevvooe): This needs a lot of work before we can call it useful.
type groupIndex struct {
byName map[string]*group
byGID map[int]*group
}
func getGroupIndex() (*groupIndex, error) {
f, err := os.Open("/etc/group")
if err != nil {
return nil, err
}
defer f.Close()
groups, err := parseGroups(f)
if err != nil {
return nil, err
}
return newGroupIndex(groups), nil
}
func newGroupIndex(groups []group) *groupIndex {
gi := &groupIndex{
byName: make(map[string]*group),
byGID: make(map[int]*group),
}
for i, group := range groups {
gi.byGID[group.gid] = &groups[i]
gi.byName[group.name] = &groups[i]
}
return gi
}
type group struct {
name string
gid int
members []string
}
func getGroupName(gid int) (string, error) {
f, err := os.Open("/etc/group")
if err != nil {
return "", err
}
defer f.Close()
groups, err := parseGroups(f)
if err != nil {
return "", err
}
for _, group := range groups {
if group.gid == gid {
return group.name, nil
}
}
return "", fmt.Errorf("no group for gid")
}
// parseGroups parses an /etc/group file for group names, ids and membership.
// This is unix specific.
func parseGroups(rd io.Reader) ([]group, error) {
var groups []group
scanner := bufio.NewScanner(rd)
for scanner.Scan() {
if strings.HasPrefix(scanner.Text(), "#") {
continue // skip comment
}
parts := strings.SplitN(scanner.Text(), ":", 4)
if len(parts) != 4 {
return nil, fmt.Errorf("bad entry: %q", scanner.Text())
}
name, _, sgid, smembers := parts[0], parts[1], parts[2], parts[3]
gid, err := strconv.Atoi(sgid)
if err != nil {
return nil, fmt.Errorf("bad gid: %q", gid)
}
members := strings.Split(smembers, ",")
groups = append(groups, group{
name: name,
gid: gid,
members: members,
})
}
if scanner.Err() != nil {
return nil, scanner.Err()
}
return groups, nil
}

57
vendor/github.com/containerd/continuity/hardlinks.go generated vendored Normal file
View file

@ -0,0 +1,57 @@
package continuity
import (
"fmt"
"os"
)
var (
errNotAHardLink = fmt.Errorf("invalid hardlink")
)
type hardlinkManager struct {
hardlinks map[hardlinkKey][]Resource
}
func newHardlinkManager() *hardlinkManager {
return &hardlinkManager{
hardlinks: map[hardlinkKey][]Resource{},
}
}
// Add attempts to add the resource to the hardlink manager. If the resource
// cannot be considered as a hardlink candidate, errNotAHardLink is returned.
func (hlm *hardlinkManager) Add(fi os.FileInfo, resource Resource) error {
if _, ok := resource.(Hardlinkable); !ok {
return errNotAHardLink
}
key, err := newHardlinkKey(fi)
if err != nil {
return err
}
hlm.hardlinks[key] = append(hlm.hardlinks[key], resource)
return nil
}
// Merge processes the current state of the hardlink manager and merges any
// shared nodes into hardlinked resources.
func (hlm *hardlinkManager) Merge() ([]Resource, error) {
var resources []Resource
for key, linked := range hlm.hardlinks {
if len(linked) < 1 {
return nil, fmt.Errorf("no hardlink entrys for dev, inode pair: %#v", key)
}
merged, err := Merge(linked...)
if err != nil {
return nil, fmt.Errorf("error merging hardlink: %v", err)
}
resources = append(resources, merged)
}
return resources, nil
}

View file

@ -0,0 +1,36 @@
// +build linux darwin freebsd solaris
package continuity
import (
"fmt"
"os"
"syscall"
)
// hardlinkKey provides a tuple-key for managing hardlinks. This is system-
// specific.
type hardlinkKey struct {
dev uint64
inode uint64
}
// newHardlinkKey returns a hardlink key for the provided file info. If the
// resource does not represent a possible hardlink, errNotAHardLink will be
// returned.
func newHardlinkKey(fi os.FileInfo) (hardlinkKey, error) {
sys, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return hardlinkKey{}, fmt.Errorf("cannot resolve (*syscall.Stat_t) from os.FileInfo")
}
if sys.Nlink < 2 {
// NOTE(stevvooe): This is not always true for all filesystems. We
// should somehow detect this and provided a slow "polyfill" that
// leverages os.SameFile if we detect a filesystem where link counts
// is not really supported.
return hardlinkKey{}, errNotAHardLink
}
return hardlinkKey{dev: uint64(sys.Dev), inode: uint64(sys.Ino)}, nil
}

View file

@ -0,0 +1,12 @@
package continuity
import "os"
type hardlinkKey struct{}
func newHardlinkKey(fi os.FileInfo) (hardlinkKey, error) {
// NOTE(stevvooe): Obviously, this is not yet implemented. However, the
// makings of an implementation are available in src/os/types_windows.go. More
// investigation needs to be done to figure out exactly how to do this.
return hardlinkKey{}, errNotAHardLink
}

47
vendor/github.com/containerd/continuity/ioutils.go generated vendored Normal file
View file

@ -0,0 +1,47 @@
package continuity
import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
)
// AtomicWriteFile atomically writes data to a file by first writing to a
// temp file and calling rename.
func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
buf := bytes.NewBuffer(data)
return atomicWriteFile(filename, buf, int64(len(data)), perm)
}
// atomicWriteFile writes data to a file by first writing to a temp
// file and calling rename.
func atomicWriteFile(filename string, r io.Reader, dataSize int64, perm os.FileMode) error {
f, err := ioutil.TempFile(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
if err != nil {
return err
}
err = os.Chmod(f.Name(), perm)
if err != nil {
f.Close()
return err
}
n, err := io.Copy(f, r)
if err == nil && n < dataSize {
f.Close()
return io.ErrShortWrite
}
if err != nil {
f.Close()
return err
}
if err := f.Sync(); err != nil {
f.Close()
return err
}
if err := f.Close(); err != nil {
return err
}
return os.Rename(f.Name(), filename)
}

144
vendor/github.com/containerd/continuity/manifest.go generated vendored Normal file
View file

@ -0,0 +1,144 @@
package continuity
import (
"fmt"
"io"
"log"
"os"
"sort"
pb "github.com/containerd/continuity/proto"
"github.com/golang/protobuf/proto"
)
// Manifest provides the contents of a manifest. Users of this struct should
// not typically modify any fields directly.
type Manifest struct {
// Resources specifies all the resources for a manifest in order by path.
Resources []Resource
}
func Unmarshal(p []byte) (*Manifest, error) {
var bm pb.Manifest
if err := proto.Unmarshal(p, &bm); err != nil {
return nil, err
}
var m Manifest
for _, b := range bm.Resource {
r, err := fromProto(b)
if err != nil {
return nil, err
}
m.Resources = append(m.Resources, r)
}
return &m, nil
}
func Marshal(m *Manifest) ([]byte, error) {
var bm pb.Manifest
for _, resource := range m.Resources {
bm.Resource = append(bm.Resource, toProto(resource))
}
return proto.Marshal(&bm)
}
func MarshalText(w io.Writer, m *Manifest) error {
var bm pb.Manifest
for _, resource := range m.Resources {
bm.Resource = append(bm.Resource, toProto(resource))
}
return proto.MarshalText(w, &bm)
}
// BuildManifest creates the manifest for the given context
func BuildManifest(ctx Context) (*Manifest, error) {
resourcesByPath := map[string]Resource{}
hardlinks := newHardlinkManager()
if err := ctx.Walk(func(p string, fi os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("error walking %s: %v", p, err)
}
if p == string(os.PathSeparator) {
// skip root
return nil
}
resource, err := ctx.Resource(p, fi)
if err != nil {
if err == ErrNotFound {
return nil
}
log.Printf("error getting resource %q: %v", p, err)
return err
}
// add to the hardlink manager
if err := hardlinks.Add(fi, resource); err == nil {
// Resource has been accepted by hardlink manager so we don't add
// it to the resourcesByPath until we merge at the end.
return nil
} else if err != errNotAHardLink {
// handle any other case where we have a proper error.
return fmt.Errorf("adding hardlink %s: %v", p, err)
}
resourcesByPath[p] = resource
return nil
}); err != nil {
return nil, err
}
// merge and post-process the hardlinks.
hardlinked, err := hardlinks.Merge()
if err != nil {
return nil, err
}
for _, resource := range hardlinked {
resourcesByPath[resource.Path()] = resource
}
var resources []Resource
for _, resource := range resourcesByPath {
resources = append(resources, resource)
}
sort.Stable(ByPath(resources))
return &Manifest{
Resources: resources,
}, nil
}
// VerifyManifest verifies all the resources in a manifest
// against files from the given context.
func VerifyManifest(ctx Context, manifest *Manifest) error {
for _, resource := range manifest.Resources {
if err := ctx.Verify(resource); err != nil {
return err
}
}
return nil
}
// ApplyManifest applies on the resources in a manifest to
// the given context.
func ApplyManifest(ctx Context, manifest *Manifest) error {
for _, resource := range manifest.Resources {
if err := ctx.Apply(resource); err != nil {
return err
}
}
return nil
}

3
vendor/github.com/containerd/continuity/proto/gen.go generated vendored Normal file
View file

@ -0,0 +1,3 @@
package proto
//go:generate protoc --go_out=. manifest.proto

View file

@ -0,0 +1,181 @@
// Code generated by protoc-gen-go.
// source: manifest.proto
// DO NOT EDIT!
/*
Package proto is a generated protocol buffer package.
It is generated from these files:
manifest.proto
It has these top-level messages:
Manifest
Resource
XAttr
ADSEntry
*/
package proto
import proto1 "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto1.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto1.ProtoPackageIsVersion2 // please upgrade the proto package
// Manifest specifies the entries in a container bundle, keyed and sorted by
// path.
type Manifest struct {
Resource []*Resource `protobuf:"bytes,1,rep,name=resource" json:"resource,omitempty"`
}
func (m *Manifest) Reset() { *m = Manifest{} }
func (m *Manifest) String() string { return proto1.CompactTextString(m) }
func (*Manifest) ProtoMessage() {}
func (*Manifest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *Manifest) GetResource() []*Resource {
if m != nil {
return m.Resource
}
return nil
}
type Resource struct {
// Path specifies the path from the bundle root. If more than one
// path is present, the entry may represent a hardlink, rather than using
// a link target. The path format is operating system specific.
Path []string `protobuf:"bytes,1,rep,name=path" json:"path,omitempty"`
// Uid specifies the user id for the resource.
Uid int64 `protobuf:"varint,2,opt,name=uid" json:"uid,omitempty"`
// Gid specifies the group id for the resource.
Gid int64 `protobuf:"varint,3,opt,name=gid" json:"gid,omitempty"`
// user and group are not currently used but their field numbers have been
// reserved for future use. As such, they are marked as deprecated.
User string `protobuf:"bytes,4,opt,name=user" json:"user,omitempty"`
Group string `protobuf:"bytes,5,opt,name=group" json:"group,omitempty"`
// Mode defines the file mode and permissions. We've used the same
// bit-packing from Go's os package,
// http://golang.org/pkg/os/#FileMode, since they've done the work of
// creating a cross-platform layout.
Mode uint32 `protobuf:"varint,6,opt,name=mode" json:"mode,omitempty"`
// Size specifies the size in bytes of the resource. This is only valid
// for regular files.
Size uint64 `protobuf:"varint,7,opt,name=size" json:"size,omitempty"`
// Digest specifies the content digest of the target file. Only valid for
// regular files. The strings are formatted in OCI style, i.e. <alg>:<encoded>.
// For detailed information about the format, please refer to OCI Image Spec:
// https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests-and-verification
// The digests are sorted in lexical order and implementations may choose
// which algorithms they prefer.
Digest []string `protobuf:"bytes,8,rep,name=digest" json:"digest,omitempty"`
// Target defines the target of a hard or soft link. Absolute links start
// with a slash and specify the resource relative to the bundle root.
// Relative links do not start with a slash and are relative to the
// resource path.
Target string `protobuf:"bytes,9,opt,name=target" json:"target,omitempty"`
// Major specifies the major device number for character and block devices.
Major uint64 `protobuf:"varint,10,opt,name=major" json:"major,omitempty"`
// Minor specifies the minor device number for character and block devices.
Minor uint64 `protobuf:"varint,11,opt,name=minor" json:"minor,omitempty"`
// Xattr provides storage for extended attributes for the target resource.
Xattr []*XAttr `protobuf:"bytes,12,rep,name=xattr" json:"xattr,omitempty"`
// Ads stores one or more alternate data streams for the target resource.
Ads []*ADSEntry `protobuf:"bytes,13,rep,name=ads" json:"ads,omitempty"`
}
func (m *Resource) Reset() { *m = Resource{} }
func (m *Resource) String() string { return proto1.CompactTextString(m) }
func (*Resource) ProtoMessage() {}
func (*Resource) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *Resource) GetXattr() []*XAttr {
if m != nil {
return m.Xattr
}
return nil
}
func (m *Resource) GetAds() []*ADSEntry {
if m != nil {
return m.Ads
}
return nil
}
// XAttr encodes extended attributes for a resource.
type XAttr struct {
// Name specifies the attribute name.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// Data specifies the associated data for the attribute.
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
}
func (m *XAttr) Reset() { *m = XAttr{} }
func (m *XAttr) String() string { return proto1.CompactTextString(m) }
func (*XAttr) ProtoMessage() {}
func (*XAttr) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
// ADSEntry encodes information for a Windows Alternate Data Stream.
type ADSEntry struct {
// Name specifices the stream name.
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// Data specifies the stream data.
// See also the description about the digest below.
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
// Digest is a CAS representation of the stream data.
//
// At least one of data or digest MUST be specified, and either one of them
// SHOULD be specified.
//
// How to access the actual data using the digest is implementation-specific,
// and implementations can choose not to implement digest.
// So, digest SHOULD be used only when the stream data is large.
Digest string `protobuf:"bytes,3,opt,name=digest" json:"digest,omitempty"`
}
func (m *ADSEntry) Reset() { *m = ADSEntry{} }
func (m *ADSEntry) String() string { return proto1.CompactTextString(m) }
func (*ADSEntry) ProtoMessage() {}
func (*ADSEntry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
func init() {
proto1.RegisterType((*Manifest)(nil), "proto.Manifest")
proto1.RegisterType((*Resource)(nil), "proto.Resource")
proto1.RegisterType((*XAttr)(nil), "proto.XAttr")
proto1.RegisterType((*ADSEntry)(nil), "proto.ADSEntry")
}
func init() { proto1.RegisterFile("manifest.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 317 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x90, 0x4f, 0x4b, 0xf3, 0x40,
0x10, 0xc6, 0x49, 0x93, 0xf4, 0x4d, 0xa7, 0xed, 0xab, 0x2c, 0x52, 0xe6, 0x18, 0x73, 0x0a, 0x08,
0x15, 0xf4, 0xe0, 0xb9, 0xa2, 0x17, 0xc1, 0xcb, 0x7a, 0xf1, 0xba, 0xba, 0x6b, 0x5c, 0x21, 0xd9,
0xb0, 0xd9, 0x80, 0xfa, 0xe5, 0xfc, 0x6a, 0x32, 0xb3, 0x69, 0xd1, 0x9b, 0xa7, 0x3c, 0xcf, 0x6f,
0xfe, 0x64, 0xf6, 0x81, 0xff, 0xad, 0xea, 0xec, 0x8b, 0x19, 0xc2, 0xb6, 0xf7, 0x2e, 0x38, 0x91,
0xf3, 0xa7, 0xba, 0x82, 0xe2, 0x7e, 0x2a, 0x88, 0x33, 0x28, 0xbc, 0x19, 0xdc, 0xe8, 0x9f, 0x0d,
0x26, 0x65, 0x5a, 0x2f, 0x2f, 0x8e, 0x62, 0xf3, 0x56, 0x4e, 0x58, 0x1e, 0x1a, 0xaa, 0xaf, 0x19,
0x14, 0x7b, 0x2c, 0x04, 0x64, 0xbd, 0x0a, 0xaf, 0x3c, 0xb5, 0x90, 0xac, 0xc5, 0x31, 0xa4, 0xa3,
0xd5, 0x38, 0x2b, 0x93, 0x3a, 0x95, 0x24, 0x89, 0x34, 0x56, 0x63, 0x1a, 0x49, 0x63, 0xb5, 0xd8,
0x40, 0x36, 0x0e, 0xc6, 0x63, 0x56, 0x26, 0xf5, 0xe2, 0x7a, 0x86, 0x89, 0x64, 0x2f, 0x10, 0xf2,
0xc6, 0xbb, 0xb1, 0xc7, 0xfc, 0x50, 0x88, 0x80, 0xfe, 0xd4, 0x3a, 0x6d, 0x70, 0x5e, 0x26, 0xf5,
0x5a, 0xb2, 0x26, 0x36, 0xd8, 0x4f, 0x83, 0xff, 0xca, 0xa4, 0xce, 0x24, 0x6b, 0xb1, 0x81, 0xb9,
0xb6, 0x8d, 0x19, 0x02, 0x16, 0x7c, 0xd3, 0xe4, 0x88, 0x07, 0xe5, 0x1b, 0x13, 0x70, 0x41, 0xab,
0xe5, 0xe4, 0xc4, 0x09, 0xe4, 0xad, 0x7a, 0x73, 0x1e, 0x81, 0x97, 0x44, 0xc3, 0xd4, 0x76, 0xce,
0xe3, 0x72, 0xa2, 0x64, 0x44, 0x05, 0xf9, 0xbb, 0x0a, 0xc1, 0xe3, 0x8a, 0x43, 0x5a, 0x4d, 0x21,
0x3d, 0xee, 0x42, 0xf0, 0x32, 0x96, 0xc4, 0x29, 0xa4, 0x4a, 0x0f, 0xb8, 0xfe, 0x15, 0xe3, 0xee,
0xe6, 0xe1, 0xb6, 0x0b, 0xfe, 0x43, 0x52, 0xad, 0x3a, 0x87, 0x9c, 0x47, 0xe8, 0xfe, 0x4e, 0xb5,
0x94, 0x39, 0x5d, 0xc4, 0x9a, 0x98, 0x56, 0x41, 0x71, 0x7c, 0x2b, 0xc9, 0xba, 0xba, 0x83, 0x62,
0xbf, 0xe1, 0xaf, 0x33, 0x3f, 0x72, 0x48, 0xe3, 0x7b, 0xa3, 0x7b, 0x9a, 0xf3, 0x45, 0x97, 0xdf,
0x01, 0x00, 0x00, 0xff, 0xff, 0xef, 0x27, 0x99, 0xf7, 0x17, 0x02, 0x00, 0x00,
}

View file

@ -0,0 +1,97 @@
syntax = "proto3";
package proto;
// Manifest specifies the entries in a container bundle, keyed and sorted by
// path.
message Manifest {
repeated Resource resource = 1;
}
message Resource {
// Path specifies the path from the bundle root. If more than one
// path is present, the entry may represent a hardlink, rather than using
// a link target. The path format is operating system specific.
repeated string path = 1;
// NOTE(stevvooe): Need to define clear precedence for user/group/uid/gid precedence.
// Uid specifies the user id for the resource.
int64 uid = 2;
// Gid specifies the group id for the resource.
int64 gid = 3;
// user and group are not currently used but their field numbers have been
// reserved for future use. As such, they are marked as deprecated.
string user = 4 [deprecated=true]; // "deprecated" stands for "reserved" here
string group = 5 [deprecated=true]; // "deprecated" stands for "reserved" here
// Mode defines the file mode and permissions. We've used the same
// bit-packing from Go's os package,
// http://golang.org/pkg/os/#FileMode, since they've done the work of
// creating a cross-platform layout.
uint32 mode = 6;
// NOTE(stevvooe): Beyond here, we start defining type specific fields.
// Size specifies the size in bytes of the resource. This is only valid
// for regular files.
uint64 size = 7;
// Digest specifies the content digest of the target file. Only valid for
// regular files. The strings are formatted in OCI style, i.e. <alg>:<encoded>.
// For detailed information about the format, please refer to OCI Image Spec:
// https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests-and-verification
// The digests are sorted in lexical order and implementations may choose
// which algorithms they prefer.
repeated string digest = 8;
// Target defines the target of a hard or soft link. Absolute links start
// with a slash and specify the resource relative to the bundle root.
// Relative links do not start with a slash and are relative to the
// resource path.
string target = 9;
// Major specifies the major device number for character and block devices.
uint64 major = 10;
// Minor specifies the minor device number for character and block devices.
uint64 minor = 11;
// Xattr provides storage for extended attributes for the target resource.
repeated XAttr xattr = 12;
// Ads stores one or more alternate data streams for the target resource.
repeated ADSEntry ads = 13;
}
// XAttr encodes extended attributes for a resource.
message XAttr {
// Name specifies the attribute name.
string name = 1;
// Data specifies the associated data for the attribute.
bytes data = 2;
}
// ADSEntry encodes information for a Windows Alternate Data Stream.
message ADSEntry {
// Name specifices the stream name.
string name = 1;
// Data specifies the stream data.
// See also the description about the digest below.
bytes data = 2;
// Digest is a CAS representation of the stream data.
//
// At least one of data or digest MUST be specified, and either one of them
// SHOULD be specified.
//
// How to access the actual data using the digest is implementation-specific,
// and implementations can choose not to implement digest.
// So, digest SHOULD be used only when the stream data is large.
string digest = 3;
}

574
vendor/github.com/containerd/continuity/resource.go generated vendored Normal file
View file

@ -0,0 +1,574 @@
package continuity
import (
"errors"
"fmt"
"os"
"reflect"
"sort"
pb "github.com/containerd/continuity/proto"
"github.com/opencontainers/go-digest"
)
// TODO(stevvooe): A record based model, somewhat sketched out at the bottom
// of this file, will be more flexible. Another possibly is to tie the package
// interface directly to the protobuf type. This will have efficiency
// advantages at the cost coupling the nasty codegen types to the exported
// interface.
type Resource interface {
// Path provides the primary resource path relative to the bundle root. In
// cases where resources have more than one path, such as with hard links,
// this will return the primary path, which is often just the first entry.
Path() string
// Mode returns the
Mode() os.FileMode
UID() int64
GID() int64
}
// ByPath provides the canonical sort order for a set of resources. Use with
// sort.Stable for deterministic sorting.
type ByPath []Resource
func (bp ByPath) Len() int { return len(bp) }
func (bp ByPath) Swap(i, j int) { bp[i], bp[j] = bp[j], bp[i] }
func (bp ByPath) Less(i, j int) bool { return bp[i].Path() < bp[j].Path() }
type XAttrer interface {
XAttrs() map[string][]byte
}
// Hardlinkable is an interface that a resource type satisfies if it can be a
// hardlink target.
type Hardlinkable interface {
// Paths returns all paths of the resource, including the primary path
// returned by Resource.Path. If len(Paths()) > 1, the resource is a hard
// link.
Paths() []string
}
type RegularFile interface {
Resource
XAttrer
Hardlinkable
Size() int64
Digests() []digest.Digest
}
// Merge two or more Resources into new file. Typically, this should be
// used to merge regular files as hardlinks. If the files are not identical,
// other than Paths and Digests, the merge will fail and an error will be
// returned.
func Merge(fs ...Resource) (Resource, error) {
if len(fs) < 1 {
return nil, fmt.Errorf("please provide a resource to merge")
}
if len(fs) == 1 {
return fs[0], nil
}
var paths []string
var digests []digest.Digest
bypath := map[string][]Resource{}
// The attributes are all compared against the first to make sure they
// agree before adding to the above collections. If any of these don't
// correctly validate, the merge fails.
prototype := fs[0]
xattrs := make(map[string][]byte)
// initialize xattrs for use below. All files must have same xattrs.
if prototypeXAttrer, ok := prototype.(XAttrer); ok {
for attr, value := range prototypeXAttrer.XAttrs() {
xattrs[attr] = value
}
}
for _, f := range fs {
h, isHardlinkable := f.(Hardlinkable)
if !isHardlinkable {
return nil, errNotAHardLink
}
if f.Mode() != prototype.Mode() {
return nil, fmt.Errorf("modes do not match: %v != %v", f.Mode(), prototype.Mode())
}
if f.UID() != prototype.UID() {
return nil, fmt.Errorf("uid does not match: %v != %v", f.UID(), prototype.UID())
}
if f.GID() != prototype.GID() {
return nil, fmt.Errorf("gid does not match: %v != %v", f.GID(), prototype.GID())
}
if xattrer, ok := f.(XAttrer); ok {
fxattrs := xattrer.XAttrs()
if !reflect.DeepEqual(fxattrs, xattrs) {
return nil, fmt.Errorf("resource %q xattrs do not match: %v != %v", f, fxattrs, xattrs)
}
}
for _, p := range h.Paths() {
pfs, ok := bypath[p]
if !ok {
// ensure paths are unique by only appending on a new path.
paths = append(paths, p)
}
bypath[p] = append(pfs, f)
}
if regFile, isRegFile := f.(RegularFile); isRegFile {
prototypeRegFile, prototypeIsRegFile := prototype.(RegularFile)
if !prototypeIsRegFile {
return nil, errors.New("prototype is not a regular file")
}
if regFile.Size() != prototypeRegFile.Size() {
return nil, fmt.Errorf("size does not match: %v != %v", regFile.Size(), prototypeRegFile.Size())
}
digests = append(digests, regFile.Digests()...)
} else if device, isDevice := f.(Device); isDevice {
prototypeDevice, prototypeIsDevice := prototype.(Device)
if !prototypeIsDevice {
return nil, errors.New("prototype is not a device")
}
if device.Major() != prototypeDevice.Major() {
return nil, fmt.Errorf("major number does not match: %v != %v", device.Major(), prototypeDevice.Major())
}
if device.Minor() != prototypeDevice.Minor() {
return nil, fmt.Errorf("minor number does not match: %v != %v", device.Minor(), prototypeDevice.Minor())
}
} else if _, isNamedPipe := f.(NamedPipe); isNamedPipe {
_, prototypeIsNamedPipe := prototype.(NamedPipe)
if !prototypeIsNamedPipe {
return nil, errors.New("prototype is not a named pipe")
}
} else {
return nil, errNotAHardLink
}
}
sort.Stable(sort.StringSlice(paths))
// Choose a "canonical" file. Really, it is just the first file to sort
// against. We also effectively select the very first digest as the
// "canonical" one for this file.
first := bypath[paths[0]][0]
resource := resource{
paths: paths,
mode: first.Mode(),
uid: first.UID(),
gid: first.GID(),
xattrs: xattrs,
}
switch typedF := first.(type) {
case RegularFile:
var err error
digests, err = uniqifyDigests(digests...)
if err != nil {
return nil, err
}
return &regularFile{
resource: resource,
size: typedF.Size(),
digests: digests,
}, nil
case Device:
return &device{
resource: resource,
major: typedF.Major(),
minor: typedF.Minor(),
}, nil
case NamedPipe:
return &namedPipe{
resource: resource,
}, nil
default:
return nil, errNotAHardLink
}
}
type Directory interface {
Resource
XAttrer
// Directory is a no-op method to identify directory objects by interface.
Directory()
}
type SymLink interface {
Resource
// Target returns the target of the symlink contained in the .
Target() string
}
type NamedPipe interface {
Resource
Hardlinkable
XAttrer
// Pipe is a no-op method to allow consistent resolution of NamedPipe
// interface.
Pipe()
}
type Device interface {
Resource
Hardlinkable
XAttrer
Major() uint64
Minor() uint64
}
type resource struct {
paths []string
mode os.FileMode
uid, gid int64
xattrs map[string][]byte
}
var _ Resource = &resource{}
func (r *resource) Path() string {
if len(r.paths) < 1 {
return ""
}
return r.paths[0]
}
func (r *resource) Mode() os.FileMode {
return r.mode
}
func (r *resource) UID() int64 {
return r.uid
}
func (r *resource) GID() int64 {
return r.gid
}
type regularFile struct {
resource
size int64
digests []digest.Digest
}
var _ RegularFile = &regularFile{}
// newRegularFile returns the RegularFile, using the populated base resource
// and one or more digests of the content.
func newRegularFile(base resource, paths []string, size int64, dgsts ...digest.Digest) (RegularFile, error) {
if !base.Mode().IsRegular() {
return nil, fmt.Errorf("not a regular file")
}
base.paths = make([]string, len(paths))
copy(base.paths, paths)
// make our own copy of digests
ds := make([]digest.Digest, len(dgsts))
copy(ds, dgsts)
return &regularFile{
resource: base,
size: size,
digests: ds,
}, nil
}
func (rf *regularFile) Paths() []string {
paths := make([]string, len(rf.paths))
copy(paths, rf.paths)
return paths
}
func (rf *regularFile) Size() int64 {
return rf.size
}
func (rf *regularFile) Digests() []digest.Digest {
digests := make([]digest.Digest, len(rf.digests))
copy(digests, rf.digests)
return digests
}
func (rf *regularFile) XAttrs() map[string][]byte {
xattrs := make(map[string][]byte, len(rf.xattrs))
for attr, value := range rf.xattrs {
xattrs[attr] = append(xattrs[attr], value...)
}
return xattrs
}
type directory struct {
resource
}
var _ Directory = &directory{}
func newDirectory(base resource) (Directory, error) {
if !base.Mode().IsDir() {
return nil, fmt.Errorf("not a directory")
}
return &directory{
resource: base,
}, nil
}
func (d *directory) Directory() {}
func (d *directory) XAttrs() map[string][]byte {
xattrs := make(map[string][]byte, len(d.xattrs))
for attr, value := range d.xattrs {
xattrs[attr] = append(xattrs[attr], value...)
}
return xattrs
}
type symLink struct {
resource
target string
}
var _ SymLink = &symLink{}
func newSymLink(base resource, target string) (SymLink, error) {
if base.Mode()&os.ModeSymlink == 0 {
return nil, fmt.Errorf("not a symlink")
}
return &symLink{
resource: base,
target: target,
}, nil
}
func (l *symLink) Target() string {
return l.target
}
type namedPipe struct {
resource
}
var _ NamedPipe = &namedPipe{}
func newNamedPipe(base resource, paths []string) (NamedPipe, error) {
if base.Mode()&os.ModeNamedPipe == 0 {
return nil, fmt.Errorf("not a namedpipe")
}
base.paths = make([]string, len(paths))
copy(base.paths, paths)
return &namedPipe{
resource: base,
}, nil
}
func (np *namedPipe) Pipe() {}
func (np *namedPipe) Paths() []string {
paths := make([]string, len(np.paths))
copy(paths, np.paths)
return paths
}
func (np *namedPipe) XAttrs() map[string][]byte {
xattrs := make(map[string][]byte, len(np.xattrs))
for attr, value := range np.xattrs {
xattrs[attr] = append(xattrs[attr], value...)
}
return xattrs
}
type device struct {
resource
major, minor uint64
}
var _ Device = &device{}
func newDevice(base resource, paths []string, major, minor uint64) (Device, error) {
if base.Mode()&os.ModeDevice == 0 {
return nil, fmt.Errorf("not a device")
}
base.paths = make([]string, len(paths))
copy(base.paths, paths)
return &device{
resource: base,
major: major,
minor: minor,
}, nil
}
func (d *device) Paths() []string {
paths := make([]string, len(d.paths))
copy(paths, d.paths)
return paths
}
func (d *device) XAttrs() map[string][]byte {
xattrs := make(map[string][]byte, len(d.xattrs))
for attr, value := range d.xattrs {
xattrs[attr] = append(xattrs[attr], value...)
}
return xattrs
}
func (d device) Major() uint64 {
return d.major
}
func (d device) Minor() uint64 {
return d.minor
}
// toProto converts a resource to a protobuf record. We'd like to push this
// the individual types but we want to keep this all together during
// prototyping.
func toProto(resource Resource) *pb.Resource {
b := &pb.Resource{
Path: []string{resource.Path()},
Mode: uint32(resource.Mode()),
Uid: resource.UID(),
Gid: resource.GID(),
}
if xattrer, ok := resource.(XAttrer); ok {
// Sorts the XAttrs by name for consistent ordering.
keys := []string{}
xattrs := xattrer.XAttrs()
for k := range xattrs {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
b.Xattr = append(b.Xattr, &pb.XAttr{Name: k, Data: xattrs[k]})
}
}
switch r := resource.(type) {
case RegularFile:
b.Path = r.Paths()
b.Size = uint64(r.Size())
for _, dgst := range r.Digests() {
b.Digest = append(b.Digest, dgst.String())
}
case SymLink:
b.Target = r.Target()
case Device:
b.Major, b.Minor = r.Major(), r.Minor()
b.Path = r.Paths()
case NamedPipe:
b.Path = r.Paths()
}
// enforce a few stability guarantees that may not be provided by the
// resource implementation.
sort.Strings(b.Path)
return b
}
// fromProto converts from a protobuf Resource to a Resource interface.
func fromProto(b *pb.Resource) (Resource, error) {
base := &resource{
paths: b.Path,
mode: os.FileMode(b.Mode),
uid: b.Uid,
gid: b.Gid,
}
base.xattrs = make(map[string][]byte, len(b.Xattr))
for _, attr := range b.Xattr {
base.xattrs[attr.Name] = attr.Data
}
switch {
case base.Mode().IsRegular():
dgsts := make([]digest.Digest, len(b.Digest))
for i, dgst := range b.Digest {
// TODO(stevvooe): Should we be validating at this point?
dgsts[i] = digest.Digest(dgst)
}
return newRegularFile(*base, b.Path, int64(b.Size), dgsts...)
case base.Mode().IsDir():
return newDirectory(*base)
case base.Mode()&os.ModeSymlink != 0:
return newSymLink(*base, b.Target)
case base.Mode()&os.ModeNamedPipe != 0:
return newNamedPipe(*base, b.Path)
case base.Mode()&os.ModeDevice != 0:
return newDevice(*base, b.Path, b.Major, b.Minor)
}
return nil, fmt.Errorf("unknown resource record (%#v): %s", b, base.Mode())
}
// NOTE(stevvooe): An alternative model that supports inline declaration.
// Convenient for unit testing where inline declarations may be desirable but
// creates an awkward API for the standard use case.
// type ResourceKind int
// const (
// ResourceRegularFile = iota + 1
// ResourceDirectory
// ResourceSymLink
// Resource
// )
// type Resource struct {
// Kind ResourceKind
// Paths []string
// Mode os.FileMode
// UID string
// GID string
// Size int64
// Digests []digest.Digest
// Target string
// Major, Minor int
// XAttrs map[string][]byte
// }
// type RegularFile struct {
// Paths []string
// Size int64
// Digests []digest.Digest
// Perm os.FileMode // os.ModePerm + sticky, setuid, setgid
// }

View file

@ -0,0 +1,37 @@
// +build linux darwin freebsd solaris
package continuity
import (
"fmt"
"os"
"syscall"
)
// newBaseResource returns a *resource, populated with data from p and fi,
// where p will be populated directly.
func newBaseResource(p string, fi os.FileInfo) (*resource, error) {
// TODO(stevvooe): This need to be resolved for the container's root,
// where here we are really getting the host OS's value. We need to allow
// this be passed in and fixed up to make these uid/gid mappings portable.
// Either this can be part of the driver or we can achieve it through some
// other mechanism.
sys, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
// TODO(stevvooe): This may not be a hard error for all platforms. We
// may want to move this to the driver.
return nil, fmt.Errorf("unable to resolve syscall.Stat_t from (os.FileInfo).Sys(): %#v", fi)
}
return &resource{
paths: []string{p},
mode: fi.Mode(),
uid: int64(sys.Uid),
gid: int64(sys.Gid),
// NOTE(stevvooe): Population of shared xattrs field is deferred to
// the resource types that populate it. Since they are a property of
// the context, they must set there.
}, nil
}

View file

@ -0,0 +1,12 @@
package continuity
import "os"
// newBaseResource returns a *resource, populated with data from p and fi,
// where p will be populated directly.
func newBaseResource(p string, fi os.FileInfo) (*resource, error) {
return &resource{
paths: []string{p},
mode: fi.Mode(),
}, nil
}

View file

@ -1,3 +1,5 @@
// +build !windows
/*
Copyright The containerd Authors.

View file

@ -20,9 +20,6 @@ import (
"io"
"os"
"os/exec"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
type IO interface {
@ -37,49 +34,22 @@ type StartCloser interface {
CloseAfterStart() error
}
// NewPipeIO creates pipe pairs to be used with runc
func NewPipeIO(uid, gid int) (i IO, err error) {
var pipes []*pipe
// cleanup in case of an error
defer func() {
if err != nil {
for _, p := range pipes {
p.Close()
}
}
}()
stdin, err := newPipe()
if err != nil {
return nil, err
}
pipes = append(pipes, stdin)
if err = unix.Fchown(int(stdin.r.Fd()), uid, gid); err != nil {
return nil, errors.Wrap(err, "failed to chown stdin")
}
// IOOpt sets I/O creation options
type IOOpt func(*IOOption)
stdout, err := newPipe()
if err != nil {
return nil, err
}
pipes = append(pipes, stdout)
if err = unix.Fchown(int(stdout.w.Fd()), uid, gid); err != nil {
return nil, errors.Wrap(err, "failed to chown stdout")
}
// IOOption holds I/O creation options
type IOOption struct {
OpenStdin bool
OpenStdout bool
OpenStderr bool
}
stderr, err := newPipe()
if err != nil {
return nil, err
func defaultIOOption() *IOOption {
return &IOOption{
OpenStdin: true,
OpenStdout: true,
OpenStderr: true,
}
pipes = append(pipes, stderr)
if err = unix.Fchown(int(stderr.w.Fd()), uid, gid); err != nil {
return nil, errors.Wrap(err, "failed to chown stderr")
}
return &pipeIO{
in: stdin,
out: stdout,
err: stderr,
}, nil
}
func newPipe() (*pipe, error) {
@ -99,9 +69,9 @@ type pipe struct {
}
func (p *pipe) Close() error {
err := p.r.Close()
if werr := p.w.Close(); err == nil {
err = werr
err := p.w.Close()
if rerr := p.r.Close(); err == nil {
err = rerr
}
return err
}
@ -113,14 +83,23 @@ type pipeIO struct {
}
func (i *pipeIO) Stdin() io.WriteCloser {
if i.in == nil {
return nil
}
return i.in.w
}
func (i *pipeIO) Stdout() io.ReadCloser {
if i.out == nil {
return nil
}
return i.out.r
}
func (i *pipeIO) Stderr() io.ReadCloser {
if i.err == nil {
return nil
}
return i.err.r
}
@ -131,28 +110,38 @@ func (i *pipeIO) Close() error {
i.out,
i.err,
} {
if cerr := v.Close(); err == nil {
err = cerr
if v != nil {
if cerr := v.Close(); err == nil {
err = cerr
}
}
}
return err
}
func (i *pipeIO) CloseAfterStart() error {
for _, f := range []*os.File{
i.out.w,
i.err.w,
for _, f := range []*pipe{
i.out,
i.err,
} {
f.Close()
if f != nil {
f.w.Close()
}
}
return nil
}
// Set sets the io to the exec.Cmd
func (i *pipeIO) Set(cmd *exec.Cmd) {
cmd.Stdin = i.in.r
cmd.Stdout = i.out.w
cmd.Stderr = i.err.w
if i.in != nil {
cmd.Stdin = i.in.r
}
if i.out != nil {
cmd.Stdout = i.out.w
}
if i.err != nil {
cmd.Stderr = i.err.w
}
}
func NewSTDIO() (IO, error) {

76
vendor/github.com/containerd/go-runc/io_unix.go generated vendored Normal file
View file

@ -0,0 +1,76 @@
// +build !windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package runc
import (
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
// NewPipeIO creates pipe pairs to be used with runc
func NewPipeIO(uid, gid int, opts ...IOOpt) (i IO, err error) {
option := defaultIOOption()
for _, o := range opts {
o(option)
}
var (
pipes []*pipe
stdin, stdout, stderr *pipe
)
// cleanup in case of an error
defer func() {
if err != nil {
for _, p := range pipes {
p.Close()
}
}
}()
if option.OpenStdin {
if stdin, err = newPipe(); err != nil {
return nil, err
}
pipes = append(pipes, stdin)
if err = unix.Fchown(int(stdin.r.Fd()), uid, gid); err != nil {
return nil, errors.Wrap(err, "failed to chown stdin")
}
}
if option.OpenStdout {
if stdout, err = newPipe(); err != nil {
return nil, err
}
pipes = append(pipes, stdout)
if err = unix.Fchown(int(stdout.w.Fd()), uid, gid); err != nil {
return nil, errors.Wrap(err, "failed to chown stdout")
}
}
if option.OpenStderr {
if stderr, err = newPipe(); err != nil {
return nil, err
}
pipes = append(pipes, stderr)
if err = unix.Fchown(int(stderr.w.Fd()), uid, gid); err != nil {
return nil, errors.Wrap(err, "failed to chown stderr")
}
}
return &pipeIO{
in: stdin,
out: stdout,
err: stderr,
}, nil
}

62
vendor/github.com/containerd/go-runc/io_windows.go generated vendored Normal file
View file

@ -0,0 +1,62 @@
// +build windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package runc
// NewPipeIO creates pipe pairs to be used with runc
func NewPipeIO(opts ...IOOpt) (i IO, err error) {
option := defaultIOOption()
for _, o := range opts {
o(option)
}
var (
pipes []*pipe
stdin, stdout, stderr *pipe
)
// cleanup in case of an error
defer func() {
if err != nil {
for _, p := range pipes {
p.Close()
}
}
}()
if option.OpenStdin {
if stdin, err = newPipe(); err != nil {
return nil, err
}
pipes = append(pipes, stdin)
}
if option.OpenStdout {
if stdout, err = newPipe(); err != nil {
return nil, err
}
pipes = append(pipes, stdout)
}
if option.OpenStderr {
if stderr, err = newPipe(); err != nil {
return nil, err
}
pipes = append(pipes, stderr)
}
return &pipeIO{
in: stdin,
out: stdout,
err: stderr,
}, nil
}

View file

@ -608,9 +608,8 @@ func parseVersion(data []byte) (Version, error) {
var v Version
parts := strings.Split(strings.TrimSpace(string(data)), "\n")
if len(parts) != 3 {
return v, ErrParseRuncVersion
return v, nil
}
for i, p := range []struct {
dest *string
split string