Update containerd to 2f69be5594.

Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
Lantao Liu 2019-06-10 19:34:46 -07:00
parent 53c71e2b10
commit efba8e147f
381 changed files with 53751 additions and 16135 deletions

View File

@ -1,13 +1,13 @@
github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
github.com/BurntSushi/toml v0.3.1
github.com/containerd/cgroups 4994991857f9b0ae8dc439551e8bebdbb4bf66c1
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
github.com/containerd/containerd 32e788a8be3ab4418265693d9e742c30495fdd4c
github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f
github.com/containerd/containerd 2f69be5594cecacdbe42076ed12c7039a0638a2c
github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
github.com/containerd/go-cni 22460c018b64cf8bf4151b3ff9c4d077e6a88cbf
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
github.com/containerd/ttrpc f02858b1457c5ca3aaec3a0803eb0d59f96e41d6
github.com/containerd/ttrpc a5bd8ce9e40bc7c065a11c6936f4d032ce6bfa2b
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
github.com/containernetworking/cni v0.6.0
github.com/containernetworking/plugins v0.7.5
@ -17,7 +17,7 @@ github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580
github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
github.com/docker/go-units v0.3.1
github.com/docker/go-units v0.4.0
github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528
github.com/emicklei/go-restful v2.2.1
github.com/godbus/dbus v3
@ -28,15 +28,15 @@ github.com/google/gofuzz 44d81051d367757e1c7c6a5a86423ece9afcf63c
github.com/grpc-ecosystem/go-grpc-prometheus v1.1
github.com/json-iterator/go 1.1.5
github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/Microsoft/go-winio c599b533b43b1363d7d7c6cfda5ede70ed73ff13
github.com/Microsoft/go-winio 84b4ab48a50763fe7b3abcef38e5205c12027fac
github.com/Microsoft/hcsshim 8abdbb8205e4192c68b5f84c31197156f31be517
github.com/modern-go/concurrent 1.0.3
github.com/modern-go/reflect2 1.0.1
github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7
github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/runc 029124da7af7360afa781a0234d1b083550f797c
github.com/opencontainers/runc v1.0.0-rc8
github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4
github.com/opencontainers/selinux v1.2.1
github.com/opencontainers/selinux v1.2.2
github.com/pkg/errors v0.8.1
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang f4fb1b73fb099f396a7f0036bf86aa8def4ed823
@ -46,19 +46,19 @@ github.com/prometheus/procfs cb4147076ac75738c9a7d279075a253c0cc5acbd
github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0
github.com/sirupsen/logrus v1.4.1
github.com/stretchr/testify v1.1.4
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
github.com/syndtr/gocapability d98352740cb2c55f81556b63d4a1ec64c5a319c2
github.com/tchap/go-patricia v2.2.6
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
go.etcd.io/bbolt v1.3.2
golang.org/x/crypto 49796115aa4b964c318aad4f3084fdb41e9aa067
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
go.etcd.io/bbolt 2eb7227adea1d5cf85f0bc2a82b7059b13c2fa68
golang.org/x/crypto 88737f569e3a9c7ab309cdc09a07fe7fc87233c3
golang.org/x/net f3200d17e092c607f615320ecaad13d87ad9a2b3
golang.org/x/oauth2 a6bd8cefa1811bd24b86f8902872e4e8225f74c4
golang.org/x/sync 42b317875d0fa942474b76e1b46a6060d720ae6e
golang.org/x/sys d455e41777fca6e8a5a79e34a14b8368bc11d9ba https://github.com/golang/sys
golang.org/x/sys 4c4f7f33c9ed00de01c4c741d2177abfcfe19307 https://github.com/golang/sys
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
google.golang.org/grpc v1.12.0
google.golang.org/grpc 25c4f928eaa6d96443009bd842389fb4fa48664e
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
gopkg.in/yaml.v2 v2.2.1
k8s.io/api kubernetes-1.15.0-alpha.0

View File

@ -3,10 +3,13 @@
package winio
import (
"context"
"errors"
"fmt"
"io"
"net"
"os"
"runtime"
"syscall"
"time"
"unsafe"
@ -18,6 +21,48 @@ import (
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
//sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) = ntdll.NtCreateNamedPipeFile
//sys rtlNtStatusToDosError(status ntstatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb
//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) = ntdll.RtlDosPathNameToNtPathName_U
//sys rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) = ntdll.RtlDefaultNpAcl
type ioStatusBlock struct {
Status, Information uintptr
}
type objectAttributes struct {
Length uintptr
RootDirectory uintptr
ObjectName *unicodeString
Attributes uintptr
SecurityDescriptor *securityDescriptor
SecurityQoS uintptr
}
type unicodeString struct {
Length uint16
MaximumLength uint16
Buffer uintptr
}
type securityDescriptor struct {
Revision byte
Sbz1 byte
Control uint16
Owner uintptr
Group uintptr
Sacl uintptr
Dacl uintptr
}
type ntstatus int32
func (status ntstatus) Err() error {
if status >= 0 {
return nil
}
return rtlNtStatusToDosError(status)
}
const (
cERROR_PIPE_BUSY = syscall.Errno(231)
@ -25,21 +70,20 @@ const (
cERROR_PIPE_CONNECTED = syscall.Errno(535)
cERROR_SEM_TIMEOUT = syscall.Errno(121)
cPIPE_ACCESS_DUPLEX = 0x3
cFILE_FLAG_FIRST_PIPE_INSTANCE = 0x80000
cSECURITY_SQOS_PRESENT = 0x100000
cSECURITY_ANONYMOUS = 0
cPIPE_REJECT_REMOTE_CLIENTS = 0x8
cPIPE_UNLIMITED_INSTANCES = 255
cNMPWAIT_USE_DEFAULT_WAIT = 0
cNMPWAIT_NOWAIT = 1
cPIPE_TYPE_MESSAGE = 4
cPIPE_READMODE_MESSAGE = 2
cFILE_OPEN = 1
cFILE_CREATE = 2
cFILE_PIPE_MESSAGE_TYPE = 1
cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2
cSE_DACL_PRESENT = 4
)
var (
@ -137,9 +181,30 @@ func (s pipeAddress) String() string {
return string(s)
}
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) {
for {
select {
case <-ctx.Done():
return syscall.Handle(0), ctx.Err()
default:
h, err := createFile(*path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
if err == nil {
return h, nil
}
if err != cERROR_PIPE_BUSY {
return h, &os.PathError{Err: err, Op: "open", Path: *path}
}
// Wait 10 msec and try again. This is a rather simplistic
// view, as we always try each 10 milliseconds.
time.Sleep(time.Millisecond * 10)
}
}
}
// DialPipe connects to a named pipe by path, timing out if the connection
// takes longer than the specified duration. If timeout is nil, then we use
// a default timeout of 5 seconds. (We do not use WaitNamedPipe.)
// a default timeout of 2 seconds. (We do not use WaitNamedPipe.)
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
var absTimeout time.Time
if timeout != nil {
@ -147,23 +212,22 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
} else {
absTimeout = time.Now().Add(time.Second * 2)
}
var err error
var h syscall.Handle
for {
h, err = createFile(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
if err != cERROR_PIPE_BUSY {
break
}
if time.Now().After(absTimeout) {
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
conn, err := DialPipeContext(ctx, path)
if err == context.DeadlineExceeded {
return nil, ErrTimeout
}
// Wait 10 msec and try again. This is a rather simplistic
// view, as we always try each 10 milliseconds.
time.Sleep(time.Millisecond * 10)
return conn, err
}
// DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
// cancellation or timeout.
func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
var err error
var h syscall.Handle
h, err = tryDialPipe(ctx, &path)
if err != nil {
return nil, &os.PathError{Op: "open", Path: path, Err: err}
return nil, err
}
var flags uint32
@ -196,41 +260,85 @@ type acceptResponse struct {
type win32PipeListener struct {
firstHandle syscall.Handle
path string
securityDescriptor []byte
config PipeConfig
acceptCh chan (chan acceptResponse)
closeCh chan int
doneCh chan int
}
func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED
if first {
flags |= cFILE_FLAG_FIRST_PIPE_INSTANCE
}
var mode uint32 = cPIPE_REJECT_REMOTE_CLIENTS
if c.MessageMode {
mode |= cPIPE_TYPE_MESSAGE
}
sa := &syscall.SecurityAttributes{}
sa.Length = uint32(unsafe.Sizeof(*sa))
if securityDescriptor != nil {
len := uint32(len(securityDescriptor))
sa.SecurityDescriptor = localAlloc(0, len)
defer localFree(sa.SecurityDescriptor)
copy((*[0xffff]byte)(unsafe.Pointer(sa.SecurityDescriptor))[:], securityDescriptor)
}
h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, sa)
func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
path16, err := syscall.UTF16FromString(path)
if err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
var oa objectAttributes
oa.Length = unsafe.Sizeof(oa)
var ntPath unicodeString
if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0).Err(); err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
defer localFree(ntPath.Buffer)
oa.ObjectName = &ntPath
// The security descriptor is only needed for the first pipe.
if first {
if sd != nil {
len := uint32(len(sd))
sdb := localAlloc(0, len)
defer localFree(sdb)
copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd)
oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb))
} else {
// Construct the default named pipe security descriptor.
var dacl uintptr
if err := rtlDefaultNpAcl(&dacl).Err(); err != nil {
return 0, fmt.Errorf("getting default named pipe ACL: %s", err)
}
defer localFree(dacl)
sdb := &securityDescriptor{
Revision: 1,
Control: cSE_DACL_PRESENT,
Dacl: dacl,
}
oa.SecurityDescriptor = sdb
}
}
typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS)
if c.MessageMode {
typ |= cFILE_PIPE_MESSAGE_TYPE
}
disposition := uint32(cFILE_OPEN)
access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE)
if first {
disposition = cFILE_CREATE
// By not asking for read or write access, the named pipe file system
// will put this pipe into an initially disconnected state, blocking
// client connections until the next call with first == false.
access = syscall.SYNCHRONIZE
}
timeout := int64(-50 * 10000) // 50ms
var (
h syscall.Handle
iosb ioStatusBlock
)
err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout).Err()
if err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
runtime.KeepAlive(ntPath)
return h, nil
}
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
h, err := makeServerPipeHandle(l.path, l.securityDescriptor, &l.config, false)
h, err := makeServerPipeHandle(l.path, nil, &l.config, false)
if err != nil {
return nil, err
}
@ -341,28 +449,9 @@ func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
if err != nil {
return nil, err
}
// Create a client handle and connect it. This results in the pipe
// instance always existing, so that clients see ERROR_PIPE_BUSY
// rather than ERROR_FILE_NOT_FOUND. This ties the first instance
// up so that no other instances can be used. This would have been
// cleaner if the Win32 API matched CreateFile with ConnectNamedPipe
// instead of CreateNamedPipe. (Apparently created named pipes are
// considered to be in listening state regardless of whether any
// active calls to ConnectNamedPipe are outstanding.)
h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
if err != nil {
syscall.Close(h)
return nil, err
}
// Close the client handle. The server side of the instance will
// still be busy, leading to ERROR_PIPE_BUSY instead of
// ERROR_NOT_FOUND, as long as we don't close the server handle,
// or disconnect the client with DisconnectNamedPipe.
syscall.Close(h2)
l := &win32PipeListener{
firstHandle: h,
path: path,
securityDescriptor: sd,
config: *c,
acceptCh: make(chan (chan acceptResponse)),
closeCh: make(chan int),

View File

@ -17,7 +17,7 @@ const (
// will always be collected.
type Level uint8
// Predefined ETW log levels.
// Predefined ETW log levels from winmeta.xml in the Windows SDK.
const (
LevelAlways Level = iota
LevelCritical
@ -27,13 +27,30 @@ const (
LevelVerbose
)
// Opcode represents the operation that the event indicates is being performed.
type Opcode uint8
// Predefined ETW opcodes from winmeta.xml in the Windows SDK.
const (
// OpcodeInfo indicates an informational event.
OpcodeInfo Opcode = iota
// OpcodeStart indicates the start of an operation.
OpcodeStart
// OpcodeStop indicates the end of an operation.
OpcodeStop
// OpcodeDCStart indicates the start of a provider capture state operation.
OpcodeDCStart
// OpcodeDCStop indicates the end of a provider capture state operation.
OpcodeDCStop
)
// EventDescriptor represents various metadata for an ETW event.
type eventDescriptor struct {
id uint16
version uint8
channel Channel
level Level
opcode uint8
opcode Opcode
task uint16
keyword uint64
}

View File

@ -1,13 +1,13 @@
package etw
import (
"golang.org/x/sys/windows"
"github.com/Microsoft/go-winio/pkg/guid"
)
type eventOptions struct {
descriptor *eventDescriptor
activityID *windows.GUID
relatedActivityID *windows.GUID
activityID *guid.GUID
relatedActivityID *guid.GUID
tags uint32
}
@ -36,12 +36,20 @@ func WithKeyword(keyword uint64) EventOpt {
}
}
// WithChannel specifies the channel of the event to be written.
func WithChannel(channel Channel) EventOpt {
return func(options *eventOptions) {
options.descriptor.channel = channel
}
}
// WithOpcode specifies the opcode of the event to be written.
func WithOpcode(opcode Opcode) EventOpt {
return func(options *eventOptions) {
options.descriptor.opcode = opcode
}
}
// WithTags specifies the tags of the event to be written. Tags is a 28-bit
// value (top 4 bits are ignored) which are interpreted by the event consumer.
func WithTags(newTags uint32) EventOpt {
@ -50,13 +58,15 @@ func WithTags(newTags uint32) EventOpt {
}
}
func WithActivityID(activityID *windows.GUID) EventOpt {
// WithActivityID specifies the activity ID of the event to be written.
func WithActivityID(activityID *guid.GUID) EventOpt {
return func(options *eventOptions) {
options.activityID = activityID
}
}
func WithRelatedActivityID(activityID *windows.GUID) EventOpt {
// WithRelatedActivityID specifies the parent activity ID of the event to be written.
func WithRelatedActivityID(activityID *guid.GUID) EventOpt {
return func(options *eventOptions) {
options.relatedActivityID = activityID
}

View File

@ -1,7 +1,9 @@
package etw
import (
"fmt"
"math"
"reflect"
"unsafe"
)
@ -377,3 +379,124 @@ func Struct(name string, opts ...FieldOpt) FieldOpt {
}
}
}
// Currently, we support logging basic builtin types (int, string, etc), slices
// of basic builtin types, error, types derived from the basic types (e.g. "type
// foo int"), and structs (recursively logging their fields). We do not support
// slices of derived types (e.g. "[]foo").
//
// For types that we don't support, the value is formatted via fmt.Sprint, and
// we also log a message that the type is unsupported along with the formatted
// type. The intent of this is to make it easier to see which types are not
// supported in traces, so we can evaluate adding support for more types in the
// future.
func SmartField(name string, v interface{}) FieldOpt {
switch v := v.(type) {
case bool:
return BoolField(name, v)
case []bool:
return BoolArray(name, v)
case string:
return StringField(name, v)
case []string:
return StringArray(name, v)
case int:
return IntField(name, v)
case []int:
return IntArray(name, v)
case int8:
return Int8Field(name, v)
case []int8:
return Int8Array(name, v)
case int16:
return Int16Field(name, v)
case []int16:
return Int16Array(name, v)
case int32:
return Int32Field(name, v)
case []int32:
return Int32Array(name, v)
case int64:
return Int64Field(name, v)
case []int64:
return Int64Array(name, v)
case uint:
return UintField(name, v)
case []uint:
return UintArray(name, v)
case uint8:
return Uint8Field(name, v)
case []uint8:
return Uint8Array(name, v)
case uint16:
return Uint16Field(name, v)
case []uint16:
return Uint16Array(name, v)
case uint32:
return Uint32Field(name, v)
case []uint32:
return Uint32Array(name, v)
case uint64:
return Uint64Field(name, v)
case []uint64:
return Uint64Array(name, v)
case uintptr:
return UintptrField(name, v)
case []uintptr:
return UintptrArray(name, v)
case float32:
return Float32Field(name, v)
case []float32:
return Float32Array(name, v)
case float64:
return Float64Field(name, v)
case []float64:
return Float64Array(name, v)
case error:
return StringField(name, v.Error())
default:
switch rv := reflect.ValueOf(v); rv.Kind() {
case reflect.Bool:
return SmartField(name, rv.Bool())
case reflect.Int:
return SmartField(name, int(rv.Int()))
case reflect.Int8:
return SmartField(name, int8(rv.Int()))
case reflect.Int16:
return SmartField(name, int16(rv.Int()))
case reflect.Int32:
return SmartField(name, int32(rv.Int()))
case reflect.Int64:
return SmartField(name, int64(rv.Int()))
case reflect.Uint:
return SmartField(name, uint(rv.Uint()))
case reflect.Uint8:
return SmartField(name, uint8(rv.Uint()))
case reflect.Uint16:
return SmartField(name, uint16(rv.Uint()))
case reflect.Uint32:
return SmartField(name, uint32(rv.Uint()))
case reflect.Uint64:
return SmartField(name, uint64(rv.Uint()))
case reflect.Uintptr:
return SmartField(name, uintptr(rv.Uint()))
case reflect.Float32:
return SmartField(name, float32(rv.Float()))
case reflect.Float64:
return SmartField(name, float64(rv.Float()))
case reflect.String:
return SmartField(name, rv.String())
case reflect.Struct:
fields := make([]FieldOpt, 0, rv.NumField())
for i := 0; i < rv.NumField(); i++ {
field := rv.Field(i)
if field.CanInterface() {
fields = append(fields, SmartField(name, field.Interface()))
}
}
return Struct(name, fields...)
}
}
return StringField(name, fmt.Sprintf("(Unsupported: %T) %v", v, v))
}

View File

@ -4,12 +4,11 @@ import (
"bytes"
"crypto/sha1"
"encoding/binary"
"encoding/hex"
"fmt"
"strings"
"unicode/utf16"
"unsafe"
"github.com/Microsoft/go-winio/pkg/guid"
"golang.org/x/sys/windows"
)
@ -17,7 +16,7 @@ import (
// name and ID (GUID), which should always have a 1:1 mapping to each other
// (e.g. don't use multiple provider names with the same ID, or vice versa).
type Provider struct {
ID *windows.GUID
ID *guid.GUID
handle providerHandle
metadata []byte
callback EnableCallback
@ -30,19 +29,7 @@ type Provider struct {
// String returns the `provider`.ID as a string
func (provider *Provider) String() string {
data1 := make([]byte, 4)
binary.BigEndian.PutUint32(data1, provider.ID.Data1)
data2 := make([]byte, 2)
binary.BigEndian.PutUint16(data2, provider.ID.Data2)
data3 := make([]byte, 2)
binary.BigEndian.PutUint16(data3, provider.ID.Data3)
return fmt.Sprintf(
"%s-%s-%s-%s-%s",
hex.EncodeToString(data1),
hex.EncodeToString(data2),
hex.EncodeToString(data3),
hex.EncodeToString(provider.ID.Data4[:2]),
hex.EncodeToString(provider.ID.Data4[2:]))
return provider.ID.String()
}
type providerHandle windows.Handle
@ -72,9 +59,9 @@ const (
// EnableCallback is the form of the callback function that receives provider
// enable/disable notifications from ETW.
type EnableCallback func(*windows.GUID, ProviderState, Level, uint64, uint64, uintptr)
type EnableCallback func(*guid.GUID, ProviderState, Level, uint64, uint64, uintptr)
func providerCallback(sourceID *windows.GUID, state ProviderState, level Level, matchAnyKeyword uint64, matchAllKeyword uint64, filterData uintptr, i uintptr) {
func providerCallback(sourceID *guid.GUID, state ProviderState, level Level, matchAnyKeyword uint64, matchAllKeyword uint64, filterData uintptr, i uintptr) {
provider := providers.getProvider(uint(i))
switch state {
@ -96,7 +83,7 @@ func providerCallback(sourceID *windows.GUID, state ProviderState, level Level,
// for provider notifications. Because Go has trouble with callback arguments of
// different size, it has only pointer-sized arguments, which are then cast to
// the appropriate types when calling providerCallback.
func providerCallbackAdapter(sourceID *windows.GUID, state uintptr, level uintptr, matchAnyKeyword uintptr, matchAllKeyword uintptr, filterData uintptr, i uintptr) uintptr {
func providerCallbackAdapter(sourceID *guid.GUID, state uintptr, level uintptr, matchAnyKeyword uintptr, matchAllKeyword uintptr, filterData uintptr, i uintptr) uintptr {
providerCallback(sourceID, ProviderState(state), Level(level), uint64(matchAnyKeyword), uint64(matchAllKeyword), filterData, i)
return 0
}
@ -108,7 +95,7 @@ func providerCallbackAdapter(sourceID *windows.GUID, state uintptr, level uintpt
// The algorithm is roughly:
// Hash = Sha1(namespace + arg.ToUpper().ToUtf16be())
// Guid = Hash[0..15], with Hash[7] tweaked according to RFC 4122
func providerIDFromName(name string) *windows.GUID {
func providerIDFromName(name string) *guid.GUID {
buffer := sha1.New()
namespace := []byte{0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8, 0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB}
@ -119,7 +106,7 @@ func providerIDFromName(name string) *windows.GUID {
sum := buffer.Sum(nil)
sum[7] = (sum[7] & 0xf) | 0x50
return &windows.GUID{
return &guid.GUID{
Data1: binary.LittleEndian.Uint32(sum[0:4]),
Data2: binary.LittleEndian.Uint16(sum[4:6]),
Data3: binary.LittleEndian.Uint16(sum[6:8]),
@ -137,7 +124,7 @@ func NewProvider(name string, callback EnableCallback) (provider *Provider, err
// provider ID to be manually specified. This is most useful when there is an
// existing provider ID that must be used to conform to existing diagnostic
// infrastructure.
func NewProviderWithID(name string, id *windows.GUID, callback EnableCallback) (provider *Provider, err error) {
func NewProviderWithID(name string, id *guid.GUID, callback EnableCallback) (provider *Provider, err error) {
providerCallbackOnce.Do(func() {
globalProviderCallback = windows.NewCallback(providerCallbackAdapter)
})
@ -151,7 +138,7 @@ func NewProviderWithID(name string, id *windows.GUID, callback EnableCallback) (
provider.ID = id
provider.callback = callback
if err := eventRegister(provider.ID, globalProviderCallback, uintptr(provider.index), &provider.handle); err != nil {
if err := eventRegister((*windows.GUID)(provider.ID), globalProviderCallback, uintptr(provider.index), &provider.handle); err != nil {
return nil, err
}
@ -247,7 +234,7 @@ func (provider *Provider) WriteEvent(name string, eventOpts []EventOpt, fieldOpt
dataBlobs = [][]byte{ed.bytes()}
}
return provider.writeEventRaw(options.descriptor, nil, nil, [][]byte{em.bytes()}, dataBlobs)
return provider.writeEventRaw(options.descriptor, options.activityID, options.relatedActivityID, [][]byte{em.bytes()}, dataBlobs)
}
// writeEventRaw writes a single ETW event from the provider. This function is
@ -259,8 +246,8 @@ func (provider *Provider) WriteEvent(name string, eventOpts []EventOpt, fieldOpt
// the ETW infrastructure.
func (provider *Provider) writeEventRaw(
descriptor *eventDescriptor,
activityID *windows.GUID,
relatedActivityID *windows.GUID,
activityID *guid.GUID,
relatedActivityID *guid.GUID,
metadataBlobs [][]byte,
dataBlobs [][]byte) error {
@ -275,5 +262,5 @@ func (provider *Provider) writeEventRaw(
dataDescriptors = append(dataDescriptors, newEventDataDescriptor(eventDataDescriptorTypeUserData, blob))
}
return eventWriteTransfer(provider.handle, descriptor, activityID, relatedActivityID, dataDescriptorCount, &dataDescriptors[0])
return eventWriteTransfer(provider.handle, descriptor, (*windows.GUID)(activityID), (*windows.GUID)(relatedActivityID), dataDescriptorCount, &dataDescriptors[0])
}

View File

@ -1,9 +1,6 @@
package etwlogrus
import (
"fmt"
"reflect"
"github.com/Microsoft/go-winio/pkg/etw"
"github.com/sirupsen/logrus"
)
@ -71,7 +68,7 @@ func (h *Hook) Fire(e *logrus.Entry) error {
fields = append(fields, etw.StringField("Message", e.Message))
for k, v := range e.Data {
fields = append(fields, getFieldOpt(k, v))
fields = append(fields, etw.SmartField(k, v))
}
return h.provider.WriteEvent(
@ -80,127 +77,6 @@ func (h *Hook) Fire(e *logrus.Entry) error {
fields)
}
// Currently, we support logging basic builtin types (int, string, etc), slices
// of basic builtin types, error, types derived from the basic types (e.g. "type
// foo int"), and structs (recursively logging their fields). We do not support
// slices of derived types (e.g. "[]foo").
//
// For types that we don't support, the value is formatted via fmt.Sprint, and
// we also log a message that the type is unsupported along with the formatted
// type. The intent of this is to make it easier to see which types are not
// supported in traces, so we can evaluate adding support for more types in the
// future.
func getFieldOpt(k string, v interface{}) etw.FieldOpt {
switch v := v.(type) {
case bool:
return etw.BoolField(k, v)
case []bool:
return etw.BoolArray(k, v)
case string:
return etw.StringField(k, v)
case []string:
return etw.StringArray(k, v)
case int:
return etw.IntField(k, v)
case []int:
return etw.IntArray(k, v)
case int8:
return etw.Int8Field(k, v)
case []int8:
return etw.Int8Array(k, v)
case int16:
return etw.Int16Field(k, v)
case []int16:
return etw.Int16Array(k, v)
case int32:
return etw.Int32Field(k, v)
case []int32:
return etw.Int32Array(k, v)
case int64:
return etw.Int64Field(k, v)
case []int64:
return etw.Int64Array(k, v)
case uint:
return etw.UintField(k, v)
case []uint:
return etw.UintArray(k, v)
case uint8:
return etw.Uint8Field(k, v)
case []uint8:
return etw.Uint8Array(k, v)
case uint16:
return etw.Uint16Field(k, v)
case []uint16:
return etw.Uint16Array(k, v)
case uint32:
return etw.Uint32Field(k, v)
case []uint32:
return etw.Uint32Array(k, v)
case uint64:
return etw.Uint64Field(k, v)
case []uint64:
return etw.Uint64Array(k, v)
case uintptr:
return etw.UintptrField(k, v)
case []uintptr:
return etw.UintptrArray(k, v)
case float32:
return etw.Float32Field(k, v)
case []float32:
return etw.Float32Array(k, v)
case float64:
return etw.Float64Field(k, v)
case []float64:
return etw.Float64Array(k, v)
case error:
return etw.StringField(k, v.Error())
default:
switch rv := reflect.ValueOf(v); rv.Kind() {
case reflect.Bool:
return getFieldOpt(k, rv.Bool())
case reflect.Int:
return getFieldOpt(k, int(rv.Int()))
case reflect.Int8:
return getFieldOpt(k, int8(rv.Int()))
case reflect.Int16:
return getFieldOpt(k, int16(rv.Int()))
case reflect.Int32:
return getFieldOpt(k, int32(rv.Int()))
case reflect.Int64:
return getFieldOpt(k, int64(rv.Int()))
case reflect.Uint:
return getFieldOpt(k, uint(rv.Uint()))
case reflect.Uint8:
return getFieldOpt(k, uint8(rv.Uint()))
case reflect.Uint16:
return getFieldOpt(k, uint16(rv.Uint()))
case reflect.Uint32:
return getFieldOpt(k, uint32(rv.Uint()))
case reflect.Uint64:
return getFieldOpt(k, uint64(rv.Uint()))
case reflect.Uintptr:
return getFieldOpt(k, uintptr(rv.Uint()))
case reflect.Float32:
return getFieldOpt(k, float32(rv.Float()))
case reflect.Float64:
return getFieldOpt(k, float64(rv.Float()))
case reflect.String:
return getFieldOpt(k, rv.String())
case reflect.Struct:
fields := make([]etw.FieldOpt, 0, rv.NumField())
for i := 0; i < rv.NumField(); i++ {
field := rv.Field(i)
if field.CanInterface() {
fields = append(fields, getFieldOpt(k, field.Interface()))
}
}
return etw.Struct(k, fields...)
}
}
return etw.StringField(k, fmt.Sprintf("(Unsupported: %T) %v", v, v))
}
// Close cleans up the hook and closes the ETW provider. If the provder was
// registered by etwlogrus, it will be closed as part of `Close`. If the
// provider was passed in, it will not be closed.

110
vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go generated vendored Normal file
View File

@ -0,0 +1,110 @@
package guid
import (
"crypto/rand"
"encoding/binary"
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/pkg/errors"
"golang.org/x/sys/windows"
)
var _ = (json.Marshaler)(&GUID{})
var _ = (json.Unmarshaler)(&GUID{})
// GUID represents a GUID/UUID. It has the same structure as
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
// that type. It is defined as its own type so that stringification and
// marshaling can be supported. The representation matches that used by native
// Windows code.
type GUID windows.GUID
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
func NewV4() (*GUID, error) {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
return nil, err
}
var g GUID
g.Data1 = binary.LittleEndian.Uint32(b[0:4])
g.Data2 = binary.LittleEndian.Uint16(b[4:6])
g.Data3 = binary.LittleEndian.Uint16(b[6:8])
copy(g.Data4[:], b[8:16])
g.Data3 = (g.Data3 & 0x0fff) | 0x4000 // Version 4 (randomly generated)
g.Data4[0] = (g.Data4[0] & 0x3f) | 0x80 // RFC4122 variant
return &g, nil
}
func (g *GUID) String() string {
return fmt.Sprintf(
"%08x-%04x-%04x-%04x-%012x",
g.Data1,
g.Data2,
g.Data3,
g.Data4[:2],
g.Data4[2:])
}
// FromString parses a string containing a GUID and returns the GUID. The only
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
// format.
func FromString(s string) (*GUID, error) {
if len(s) != 36 {
return nil, errors.New("invalid GUID format (length)")
}
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return nil, errors.New("invalid GUID format (dashes)")
}
var g GUID
data1, err := strconv.ParseUint(s[0:8], 16, 32)
if err != nil {
return nil, errors.Wrap(err, "invalid GUID format (Data1)")
}
g.Data1 = uint32(data1)
data2, err := strconv.ParseUint(s[9:13], 16, 16)
if err != nil {
return nil, errors.Wrap(err, "invalid GUID format (Data2)")
}
g.Data2 = uint16(data2)
data3, err := strconv.ParseUint(s[14:18], 16, 16)
if err != nil {
return nil, errors.Wrap(err, "invalid GUID format (Data3)")
}
g.Data3 = uint16(data3)
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
v, err := strconv.ParseUint(s[x:x+2], 16, 8)
if err != nil {
return nil, errors.Wrap(err, "invalid GUID format (Data4)")
}
g.Data4[i] = uint8(v)
}
return &g, nil
}
// MarshalJSON marshals the GUID to JSON representation and returns it as a
// slice of bytes.
func (g *GUID) MarshalJSON() ([]byte, error) {
return json.Marshal(g.String())
}
// UnmarshalJSON unmarshals a GUID from JSON representation and sets itself to
// the unmarshaled GUID.
func (g *GUID) UnmarshalJSON(data []byte) error {
g2, err := FromString(strings.Trim(string(data), "\""))
if err != nil {
return err
}
*g = *g2
return nil
}

View File

@ -1,4 +1,4 @@
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
// Code generated by 'go generate'; DO NOT EDIT.
package winio
@ -38,6 +38,7 @@ func errnoErr(e syscall.Errno) error {
var (
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
modntdll = windows.NewLazySystemDLL("ntdll.dll")
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
@ -47,10 +48,13 @@ var (
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procCreateFileW = modkernel32.NewProc("CreateFileW")
procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW")
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
procLocalAlloc = modkernel32.NewProc("LocalAlloc")
procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile")
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U")
procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl")
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
@ -176,27 +180,6 @@ func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityA
return
}
func waitNamedPipe(name string, timeout uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _waitNamedPipe(_p0, timeout)
}
func _waitNamedPipe(name *uint16, timeout uint32) (err error) {
r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0)
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
if r1 == 0 {
@ -227,6 +210,32 @@ func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
return
}
func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) {
r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0)
status = ntstatus(r0)
return
}
func rtlNtStatusToDosError(status ntstatus) (winerr error) {
r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0)
if r0 != 0 {
winerr = syscall.Errno(r0)
}
return
}
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) {
r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0)
status = ntstatus(r0)
return
}
func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) {
r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0)
status = ntstatus(r0)
return
}
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(accountName)

View File

@ -1,6 +1,7 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
@ -175,24 +176,13 @@
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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
https://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,

View File

@ -15,3 +15,13 @@ if err := current.SetRaw(); err != nil {
ws, err := current.Size()
current.Resize(ws)
```
## Project details
console is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE).
As a containerd sub-project, you will find the:
* [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md),
* [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS),
* and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
information in our [`containerd/project`](https://github.com/containerd/project) repository.

View File

@ -1,4 +1,4 @@
![containerd banner](https://raw.githubusercontent.com/cncf/artwork/master/containerd/horizontal/color/containerd-horizontal-color.png)
![containerd banner](https://raw.githubusercontent.com/cncf/artwork/master/projects/containerd/horizontal/color/containerd-horizontal-color.png)
[![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)

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,15 @@ service Leases {
// List lists all active leases, returning the full list of
// leases and optionally including the referenced resources.
rpc List(ListRequest) returns (ListResponse);
// AddResource references the resource by the provided lease.
rpc AddResource(AddResourceRequest) returns (google.protobuf.Empty);
// DeleteResource dereferences the resource by the provided lease.
rpc DeleteResource(DeleteResourceRequest) returns (google.protobuf.Empty);
// ListResources lists all the resources referenced by the lease.
rpc ListResources(ListResourcesRequest) returns (ListResourcesResponse);
}
// Lease is an object which retains resources while it exists.
@ -62,3 +71,32 @@ message ListRequest {
message ListResponse {
repeated Lease leases = 1;
}
message Resource {
string id = 1;
// For snapshotter resource, there are many snapshotter types here, like
// overlayfs, devmapper etc. The type will be formatted with type,
// like "snapshotter/overlayfs".
string type = 2;
}
message AddResourceRequest {
string id = 1;
Resource resource = 2 [(gogoproto.nullable) = false];
}
message DeleteResourceRequest {
string id = 1;
Resource resource = 2 [(gogoproto.nullable) = false];
}
message ListResourcesRequest {
string id = 1;
}
message ListResourcesResponse {
repeated Resource resources = 1 [(gogoproto.nullable) = false];
}

View File

@ -102,7 +102,7 @@ func New(address string, opts ...ClientOpt) (*Client, error) {
grpc.WithInsecure(),
grpc.FailOnNonTempDialError(true),
grpc.WithBackoffMaxDelay(3 * time.Second),
grpc.WithDialer(dialer.Dialer),
grpc.WithContextDialer(dialer.ContextDialer),
// TODO(stevvooe): We may need to allow configuration of this on the client.
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)),
@ -225,7 +225,7 @@ func (c *Client) IsServing(ctx context.Context) (bool, error) {
return false, errors.New("no grpc connection available")
}
c.connMu.Unlock()
r, err := c.HealthService().Check(ctx, &grpc_health_v1.HealthCheckRequest{}, grpc.FailFast(false))
r, err := c.HealthService().Check(ctx, &grpc_health_v1.HealthCheckRequest{}, grpc.WaitForReady(true))
if err != nil {
return false, err
}
@ -622,6 +622,13 @@ func (c *Client) VersionService() versionservice.VersionClient {
return versionservice.NewVersionClient(c.conn)
}
// Conn returns the underlying GRPC connection object
func (c *Client) Conn() *grpc.ClientConn {
c.connMu.Lock()
defer c.connMu.Unlock()
return c.conn
}
// Version of containerd
type Version struct {
// Version number

View File

@ -60,7 +60,7 @@ var configCommand = cli.Command{
if p.Config == nil {
continue
}
config.Plugins[p.ID] = p.Config
config.Plugins[p.URI()] = p.Config
}
}
_, err = config.WriteTo(os.Stdout)

View File

@ -23,6 +23,7 @@ import (
func defaultConfig() *srvconfig.Config {
return &srvconfig.Config{
Version: 2,
Root: defaults.DefaultRootDir,
State: defaults.DefaultStateDir,
GRPC: srvconfig.GRPCConfig{

View File

@ -25,6 +25,7 @@ import (
func defaultConfig() *srvconfig.Config {
return &srvconfig.Config{
Version: 2,
Root: defaults.DefaultRootDir,
State: defaults.DefaultStateDir,
GRPC: srvconfig.GRPCConfig{

View File

@ -23,6 +23,7 @@ import (
func defaultConfig() *srvconfig.Config {
return &srvconfig.Config{
Version: 2,
Root: defaults.DefaultRootDir,
State: defaults.DefaultStateDir,
GRPC: srvconfig.GRPCConfig{

View File

@ -278,7 +278,7 @@ func setLevel(context *cli.Context, config *srvconfig.Config) error {
return nil
}
func dumpStacks() {
func dumpStacks(writeToFile bool) {
var (
buf []byte
stackSize int
@ -291,4 +291,16 @@ func dumpStacks() {
}
buf = buf[:stackSize]
logrus.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf)
if writeToFile {
// Also write to file to aid gathering diagnostics
name := filepath.Join(os.TempDir(), fmt.Sprintf("containerd.%d.stacks.log", os.Getpid()))
f, err := os.Create(name)
if err != nil {
return
}
defer f.Close()
f.WriteString(string(buf))
logrus.Infof("goroutine stack dump written to %s", name)
}
}

View File

@ -48,7 +48,7 @@ func handleSignals(ctx context.Context, signals chan os.Signal, serverC chan *se
log.G(ctx).WithField("signal", s).Debug("received signal")
switch s {
case unix.SIGUSR1:
dumpStacks()
dumpStacks(true)
case unix.SIGPIPE:
continue
default:

View File

@ -24,7 +24,9 @@ import (
"unsafe"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/go-winio/pkg/etw"
"github.com/Microsoft/go-winio/pkg/etwlogrus"
"github.com/Microsoft/go-winio/pkg/guid"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/services/server"
"github.com/sirupsen/logrus"
@ -86,16 +88,30 @@ func setupDumpStacks() {
logrus.Debugf("Stackdump - waiting signal at %s", event)
for {
windows.WaitForSingleObject(h, windows.INFINITE)
dumpStacks()
dumpStacks(true)
}
}()
}
func etwCallback(sourceID *guid.GUID, state etw.ProviderState, level etw.Level, matchAnyKeyword uint64, matchAllKeyword uint64, filterData uintptr) {
if state == etw.ProviderStateCaptureState {
dumpStacks(false)
}
}
func init() {
// Provider ID: {2acb92c0-eb9b-571a-69cf-8f3410f383ad}
// Hook isn't closed explicitly, as it will exist until process exit.
// GUID is generated based on name - see Microsoft/go-winio/tools/etw-provider-gen.
if hook, err := etwlogrus.NewHook("ContainerD"); err == nil {
// Provider ID: 2acb92c0-eb9b-571a-69cf-8f3410f383ad
// Provider and hook aren't closed explicitly, as they will exist until
// process exit. GUID is generated based on name - see
// Microsoft/go-winio/tools/etw-provider-gen.
provider, err := etw.NewProvider("ContainerD", etwCallback)
if err != nil {
logrus.Error(err)
} else {
if hook, err := etwlogrus.NewHookFromProvider(provider); err == nil {
logrus.AddHook(hook)
} else {
logrus.Error(err)
}
}
}

View File

@ -84,18 +84,18 @@ func getEventPayload(r io.Reader) (*types.Any, error) {
}
func connectEvents(address string) (eventsapi.EventsClient, error) {
conn, err := connect(address, dialer.Dialer)
conn, err := connect(address, dialer.ContextDialer)
if err != nil {
return nil, errors.Wrapf(err, "failed to dial %q", address)
}
return eventsapi.NewEventsClient(conn), nil
}
func connect(address string, d func(string, time.Duration) (net.Conn, error)) (*grpc.ClientConn, error) {
func connect(address string, d func(gocontext.Context, string) (net.Conn, error)) (*grpc.ClientConn, error) {
gopts := []grpc.DialOption{
grpc.WithBlock(),
grpc.WithInsecure(),
grpc.WithDialer(d),
grpc.WithContextDialer(d),
grpc.FailOnNonTempDialError(true),
grpc.WithBackoffMaxDelay(3 * time.Second),
}

View File

@ -270,8 +270,8 @@ func registerService() error {
Delay uint32
}
t := []scAction{
{Type: scActionRestart, Delay: uint32(60 * time.Second / time.Millisecond)},
{Type: scActionRestart, Delay: uint32(60 * time.Second / time.Millisecond)},
{Type: scActionRestart, Delay: uint32(15 * time.Second / time.Millisecond)},
{Type: scActionRestart, Delay: uint32(15 * time.Second / time.Millisecond)},
{Type: scActionNone},
}
lpInfo := serviceFailureActions{ResetPeriod: uint32(24 * time.Hour / time.Second), ActionsCount: uint32(3), Actions: uintptr(unsafe.Pointer(&t[0]))}

View File

@ -0,0 +1,102 @@
/*
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 containers
import (
"fmt"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/errdefs"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var checkpointCommand = cli.Command{
Name: "checkpoint",
Usage: "checkpoint a container",
ArgsUsage: "CONTAINER REF",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "rw",
Usage: "include the rw layer in the checkpoint",
},
cli.BoolFlag{
Name: "image",
Usage: "include the image in the checkpoint",
},
cli.BoolFlag{
Name: "task",
Usage: "checkpoint container task",
},
},
Action: func(context *cli.Context) error {
id := context.Args().First()
if id == "" {
return errors.New("container id must be provided")
}
ref := context.Args().Get(1)
if ref == "" {
return errors.New("ref must be provided")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
opts := []containerd.CheckpointOpts{
containerd.WithCheckpointRuntime,
}
if context.Bool("image") {
opts = append(opts, containerd.WithCheckpointImage)
}
if context.Bool("rw") {
opts = append(opts, containerd.WithCheckpointRW)
}
if context.Bool("task") {
opts = append(opts, containerd.WithCheckpointTask)
}
container, err := client.LoadContainer(ctx, id)
if err != nil {
return err
}
task, err := container.Task(ctx, nil)
if err != nil {
if !errdefs.IsNotFound(err) {
return err
}
}
// pause if running
if task != nil {
if err := task.Pause(ctx); err != nil {
return err
}
defer func() {
if err := task.Resume(ctx); err != nil {
fmt.Println(errors.Wrap(err, "error resuming task"))
}
}()
}
if _, err := container.Checkpoint(ctx, ref, opts...); err != nil {
return err
}
return nil
},
}

View File

@ -28,7 +28,6 @@ import (
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/cmd/ctr/commands/run"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/log"
"github.com/containerd/typeurl"
"github.com/pkg/errors"
@ -125,7 +124,7 @@ var listCommand = cli.Command{
w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t")
for _, c := range containers {
info, err := c.Info(ctx)
info, err := c.Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil {
return err
}
@ -262,7 +261,7 @@ var infoCommand = cli.Command{
if err != nil {
return err
}
info, err := container.Info(ctx)
info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil {
return err
}
@ -285,152 +284,3 @@ var infoCommand = cli.Command{
return nil
},
}
var checkpointCommand = cli.Command{
Name: "checkpoint",
Usage: "checkpoint a container",
ArgsUsage: "CONTAINER REF",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "rw",
Usage: "include the rw layer in the checkpoint",
},
cli.BoolFlag{
Name: "image",
Usage: "include the image in the checkpoint",
},
cli.BoolFlag{
Name: "task",
Usage: "checkpoint container task",
},
},
Action: func(context *cli.Context) error {
id := context.Args().First()
if id == "" {
return errors.New("container id must be provided")
}
ref := context.Args().Get(1)
if ref == "" {
return errors.New("ref must be provided")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
opts := []containerd.CheckpointOpts{
containerd.WithCheckpointRuntime,
}
if context.Bool("image") {
opts = append(opts, containerd.WithCheckpointImage)
}
if context.Bool("rw") {
opts = append(opts, containerd.WithCheckpointRW)
}
if context.Bool("task") {
opts = append(opts, containerd.WithCheckpointTask)
}
container, err := client.LoadContainer(ctx, id)
if err != nil {
return err
}
task, err := container.Task(ctx, nil)
if err != nil {
if !errdefs.IsNotFound(err) {
return err
}
}
// pause if running
if task != nil {
if err := task.Pause(ctx); err != nil {
return err
}
defer func() {
if err := task.Resume(ctx); err != nil {
fmt.Println(errors.Wrap(err, "error resuming task"))
}
}()
}
if _, err := container.Checkpoint(ctx, ref, opts...); err != nil {
return err
}
return nil
},
}
var restoreCommand = cli.Command{
Name: "restore",
Usage: "restore a container from checkpoint",
ArgsUsage: "CONTAINER REF",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "rw",
Usage: "restore the rw layer from the checkpoint",
},
cli.BoolFlag{
Name: "live",
Usage: "restore the runtime and memory data from the checkpoint",
},
},
Action: func(context *cli.Context) error {
id := context.Args().First()
if id == "" {
return errors.New("container id must be provided")
}
ref := context.Args().Get(1)
if ref == "" {
return errors.New("ref must be provided")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
checkpoint, err := client.GetImage(ctx, ref)
if err != nil {
if !errdefs.IsNotFound(err) {
return err
}
// TODO (ehazlett): consider other options (always/never fetch)
ck, err := client.Fetch(ctx, ref)
if err != nil {
return err
}
checkpoint = containerd.NewImage(client, ck)
}
opts := []containerd.RestoreOpts{
containerd.WithRestoreImage,
containerd.WithRestoreSpec,
containerd.WithRestoreRuntime,
}
if context.Bool("rw") {
opts = append(opts, containerd.WithRestoreRW)
}
ctr, err := client.Restore(ctx, id, checkpoint, opts...)
if err != nil {
return err
}
topts := []containerd.NewTaskOpts{}
if context.Bool("live") {
topts = append(topts, containerd.WithTaskCheckpoint(checkpoint))
}
task, err := ctr.NewTask(ctx, cio.NewCreator(cio.WithStdio), topts...)
if err != nil {
return err
}
if err := task.Start(ctx); err != nil {
return err
}
return nil
},
}

View File

@ -0,0 +1,96 @@
/*
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 containers
import (
"github.com/containerd/containerd"
"github.com/containerd/containerd/cio"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/errdefs"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var restoreCommand = cli.Command{
Name: "restore",
Usage: "restore a container from checkpoint",
ArgsUsage: "CONTAINER REF",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "rw",
Usage: "restore the rw layer from the checkpoint",
},
cli.BoolFlag{
Name: "live",
Usage: "restore the runtime and memory data from the checkpoint",
},
},
Action: func(context *cli.Context) error {
id := context.Args().First()
if id == "" {
return errors.New("container id must be provided")
}
ref := context.Args().Get(1)
if ref == "" {
return errors.New("ref must be provided")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
checkpoint, err := client.GetImage(ctx, ref)
if err != nil {
if !errdefs.IsNotFound(err) {
return err
}
// TODO (ehazlett): consider other options (always/never fetch)
ck, err := client.Fetch(ctx, ref)
if err != nil {
return err
}
checkpoint = containerd.NewImage(client, ck)
}
opts := []containerd.RestoreOpts{
containerd.WithRestoreImage,
containerd.WithRestoreSpec,
containerd.WithRestoreRuntime,
}
if context.Bool("rw") {
opts = append(opts, containerd.WithRestoreRW)
}
ctr, err := client.Restore(ctx, id, checkpoint, opts...)
if err != nil {
return err
}
topts := []containerd.NewTaskOpts{}
if context.Bool("live") {
topts = append(topts, containerd.WithTaskCheckpoint(checkpoint))
}
task, err := ctr.NewTask(ctx, cio.NewCreator(cio.WithStdio), topts...)
if err != nil {
return err
}
return task.Start(ctx)
},
}

View File

@ -21,9 +21,8 @@ import (
"os"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/images/oci"
"github.com/containerd/containerd/reference"
digest "github.com/opencontainers/go-digest"
"github.com/containerd/containerd/images/archive"
"github.com/containerd/containerd/platforms"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/urfave/cli"
@ -31,26 +30,24 @@ import (
var exportCommand = cli.Command{
Name: "export",
Usage: "export an image",
ArgsUsage: "[flags] <out> <image>",
Description: `Export an image to a tar stream.
Currently, only OCI format is supported.
Usage: "export images",
ArgsUsage: "[flags] <out> <image> ...",
Description: `Export images to an OCI tar archive.
Tar output is formatted as an OCI archive, a Docker manifest is provided for the platform.
Use '--skip-manifest-json' to avoid including the Docker manifest.json file.
Use '--platform' to define the output platform.
When '--all-platforms' is given all images in a manifest list must be available.
`,
Flags: []cli.Flag{
// TODO(AkihiroSuda): make this map[string]string as in moby/moby#33355?
cli.StringFlag{
Name: "oci-ref-name",
Value: "",
Usage: "override org.opencontainers.image.ref.name annotation",
cli.BoolFlag{
Name: "skip-manifest-json",
Usage: "do not add Docker compatible manifest.json to archive",
},
cli.StringFlag{
Name: "manifest",
Usage: "digest of manifest",
},
cli.StringFlag{
Name: "manifest-type",
Usage: "media type of manifest digest",
Value: ocispec.MediaTypeImageManifest,
cli.StringSliceFlag{
Name: "platform",
Usage: "Pull content from a specific platform",
Value: &cli.StringSlice{},
},
cli.BoolFlag{
Name: "all-platforms",
@ -60,42 +57,46 @@ Currently, only OCI format is supported.
Action: func(context *cli.Context) error {
var (
out = context.Args().First()
local = context.Args().Get(1)
desc ocispec.Descriptor
images = context.Args().Tail()
exportOpts = []archive.ExportOpt{}
)
if out == "" || local == "" {
if out == "" || len(images) == 0 {
return errors.New("please provide both an output filename and an image reference to export")
}
if pss := context.StringSlice("platform"); len(pss) > 0 {
var all []ocispec.Platform
for _, ps := range pss {
p, err := platforms.Parse(ps)
if err != nil {
return errors.Wrapf(err, "invalid platform %q", ps)
}
all = append(all, p)
}
exportOpts = append(exportOpts, archive.WithPlatform(platforms.Ordered(all...)))
} else {
exportOpts = append(exportOpts, archive.WithPlatform(platforms.Default()))
}
if context.Bool("all-platforms") {
exportOpts = append(exportOpts, archive.WithAllPlatforms())
}
if context.Bool("skip-manifest-json") {
exportOpts = append(exportOpts, archive.WithSkipDockerManifest())
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
if manifest := context.String("manifest"); manifest != "" {
desc.Digest, err = digest.Parse(manifest)
if err != nil {
return errors.Wrap(err, "invalid manifest digest")
}
desc.MediaType = context.String("manifest-type")
} else {
img, err := client.ImageService().Get(ctx, local)
if err != nil {
return errors.Wrap(err, "unable to resolve image to manifest")
}
desc = img.Target
is := client.ImageService()
for _, img := range images {
exportOpts = append(exportOpts, archive.WithImage(is, img))
}
if desc.Annotations == nil {
desc.Annotations = make(map[string]string)
}
if s, ok := desc.Annotations[ocispec.AnnotationRefName]; !ok || s == "" {
if ociRefName := determineOCIRefName(local); ociRefName != "" {
desc.Annotations[ocispec.AnnotationRefName] = ociRefName
}
if ociRefName := context.String("oci-ref-name"); ociRefName != "" {
desc.Annotations[ocispec.AnnotationRefName] = ociRefName
}
}
var w io.WriteCloser
if out == "-" {
w = os.Stdout
@ -105,32 +106,8 @@ Currently, only OCI format is supported.
return nil
}
}
defer w.Close()
var (
exportOpts []oci.V1ExporterOpt
)
exportOpts = append(exportOpts, oci.WithAllPlatforms(context.Bool("all-platforms")))
r, err := client.Export(ctx, desc, exportOpts...)
if err != nil {
return err
}
if _, err := io.Copy(w, r); err != nil {
return err
}
if err := w.Close(); err != nil {
return err
}
return r.Close()
return client.Export(ctx, w, exportOpts...)
},
}
func determineOCIRefName(local string) string {
refspec, err := reference.Parse(local)
if err != nil {
return ""
}
tag, _ := reference.SplitObject(refspec.Object)
return tag
}

View File

@ -68,6 +68,10 @@ If foobar.tar contains an OCI ref named "latest" and anonymous ref "sha256:deadb
Name: "all-platforms",
Usage: "imports content for all platforms, false by default",
},
cli.BoolFlag{
Name: "no-unpack",
Usage: "skip unpacking the images, false by default",
},
}, commands.SnapshotterFlags...),
Action: func(context *cli.Context) error {
@ -119,6 +123,7 @@ If foobar.tar contains an OCI ref named "latest" and anonymous ref "sha256:deadb
return closeErr
}
if !context.Bool("no-unpack") {
log.G(ctx).Debugf("unpacking %d images", len(imgs))
for _, img := range imgs {
@ -133,6 +138,7 @@ If foobar.tar contains an OCI ref named "latest" and anonymous ref "sha256:deadb
}
fmt.Println("done")
}
}
return nil
},
}

View File

@ -115,6 +115,10 @@ var Command = cli.Command{
Name: "cgroup",
Usage: "cgroup path (To disable use of cgroup, set to \"\" explicitly)",
},
cli.StringFlag{
Name: "platform",
Usage: "run image for specific platform",
},
}, append(platformRunFlags, append(commands.SnapshotterFlags, commands.ContainerFlags...)...)...),
Action: func(context *cli.Context) error {
var (

View File

@ -27,6 +27,7 @@ import (
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/contrib/nvidia"
"github.com/containerd/containerd/oci"
"github.com/containerd/containerd/platforms"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/urfave/cli"
@ -73,10 +74,21 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli
opts = append(opts, oci.WithRootFSPath(rootfs))
} else {
snapshotter := context.String("snapshotter")
image, err := client.GetImage(ctx, ref)
var image containerd.Image
i, err := client.ImageService().Get(ctx, ref)
if err != nil {
return nil, err
}
if ps := context.String("platform"); ps != "" {
platform, err := platforms.Parse(ps)
if err != nil {
return nil, err
}
image = containerd.NewImageWithPlatform(client, i, platforms.Only(platform))
} else {
image = containerd.NewImage(client, i)
}
unpacked, err := image.IsUnpacked(ctx, snapshotter)
if err != nil {
return nil, err

View File

@ -23,9 +23,12 @@ import (
"fmt"
"io/ioutil"
"net"
"path/filepath"
"github.com/containerd/console"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/runtime/v2/shim"
"github.com/containerd/containerd/runtime/v2/task"
"github.com/containerd/ttrpc"
"github.com/containerd/typeurl"
@ -61,8 +64,8 @@ var Command = cli.Command{
Usage: "interact with a shim directly",
Flags: []cli.Flag{
cli.StringFlag{
Name: "socket",
Usage: "socket on which to connect to the shim",
Name: "id",
Usage: "container id",
},
},
Subcommands: []cli.Command{
@ -116,7 +119,7 @@ var stateCommand = cli.Command{
return err
}
r, err := service.State(gocontext.Background(), &task.StateRequest{
ID: context.Args().First(),
ID: context.GlobalString("id"),
})
if err != nil {
return err
@ -226,16 +229,22 @@ var execCommand = cli.Command{
}
func getTaskService(context *cli.Context) (task.TaskService, error) {
bindSocket := context.GlobalString("socket")
if bindSocket == "" {
return nil, errors.New("socket path must be specified")
id := context.GlobalString("id")
if id == "" {
return nil, fmt.Errorf("container id must be specified")
}
ns := context.GlobalString("namespace")
conn, err := net.Dial("unix", "\x00"+bindSocket)
if err != nil {
return nil, err
}
// /containerd-shim/ns/id/shim.sock is the old way to generate shim socket,
// compatible it
s1 := filepath.Join(string(filepath.Separator), "containerd-shim", ns, id, "shim.sock")
// this should not error, ctr always get a default ns
ctx := namespaces.WithNamespace(gocontext.Background(), ns)
s2, _ := shim.SocketAddress(ctx, id)
for _, socket := range []string{s1, s2} {
conn, err := net.Dial("unix", "\x00"+socket)
if err == nil {
client := ttrpc.NewClient(conn)
// TODO(stevvooe): This actually leaks the connection. We were leaking it
@ -243,3 +252,7 @@ func getTaskService(context *cli.Context) (task.TaskService, error) {
return task.NewTaskClient(client), nil
}
}
return nil, fmt.Errorf("fail to connect to container %s's shim", id)
}

View File

@ -49,7 +49,7 @@ type Container interface {
// ID identifies the container
ID() string
// Info returns the underlying container record type
Info(context.Context) (containers.Container, error)
Info(context.Context, ...InfoOpts) (containers.Container, error)
// Delete removes the container
Delete(context.Context, ...DeleteOpts) error
// NewTask creates a new task based on the container metadata
@ -82,6 +82,7 @@ func containerFromRecord(client *Client, c containers.Container) *container {
return &container{
client: client,
id: c.ID,
metadata: c,
}
}
@ -90,6 +91,7 @@ var _ = (Container)(&container{})
type container struct {
client *Client
id string
metadata containers.Container
}
// ID returns the container's unique id
@ -97,8 +99,22 @@ func (c *container) ID() string {
return c.id
}
func (c *container) Info(ctx context.Context) (containers.Container, error) {
return c.get(ctx)
func (c *container) Info(ctx context.Context, opts ...InfoOpts) (containers.Container, error) {
i := &InfoConfig{
// default to refreshing the container's local metadata
Refresh: true,
}
for _, o := range opts {
o(i)
}
if i.Refresh {
metadata, err := c.get(ctx)
if err != nil {
return c.metadata, err
}
c.metadata = metadata
}
return c.metadata, nil
}
func (c *container) Extensions(ctx context.Context) (map[string]prototypes.Any, error) {

View File

@ -41,6 +41,15 @@ type NewContainerOpts func(ctx context.Context, client *Client, c *containers.Co
// UpdateContainerOpts allows the caller to set additional options when updating a container
type UpdateContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error
// InfoOpts controls how container metadata is fetched and returned
type InfoOpts func(*InfoConfig)
// InfoConfig specifies how container metadata is fetched
type InfoConfig struct {
// Refresh will to a fetch of the latest container metadata
Refresh bool
}
// WithRuntime allows a user to specify the runtime name and additional options that should
// be used to create tasks for the container
func WithRuntime(name string, options interface{}) NewContainerOpts {
@ -123,7 +132,7 @@ func WithSnapshot(id string) NewContainerOpts {
// root filesystem in read-write mode
func WithNewSnapshot(id string, i Image, opts ...snapshots.Opt) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error {
diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default())
diffIDs, err := i.RootFS(ctx)
if err != nil {
return err
}
@ -235,3 +244,8 @@ func WithSpec(s *oci.Spec, opts ...oci.SpecOpts) NewContainerOpts {
return err
}
}
// WithoutRefreshedMetadata will use the current metadata attached to the container object
func WithoutRefreshedMetadata(i *InfoConfig) {
i.Refresh = false
}

View File

@ -55,7 +55,14 @@ func ReadBlob(ctx context.Context, provider Provider, desc ocispec.Descriptor) (
p := make([]byte, ra.Size())
_, err = ra.ReadAt(p, 0)
n, err := ra.ReadAt(p, 0)
if err == io.EOF {
if int64(n) != ra.Size() {
err = io.ErrUnexpectedEOF
} else {
err = nil
}
}
return p, err
}

View File

@ -35,7 +35,6 @@ import (
"github.com/containerd/containerd/log"
"github.com/sirupsen/logrus"
"github.com/containerd/continuity"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
@ -661,6 +660,19 @@ func writeTimestampFile(p string, t time.Time) error {
if err != nil {
return err
}
return continuity.AtomicWriteFile(p, b, 0666)
return atomicWrite(p, b, 0666)
}
func atomicWrite(path string, data []byte, mode os.FileMode) error {
tmp := fmt.Sprintf("%s.tmp", path)
f, err := os.OpenFile(tmp, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_SYNC, mode)
if err != nil {
return errors.Wrap(err, "create tmp file")
}
_, err = f.Write(data)
f.Close()
if err != nil {
return errors.Wrap(err, "write atomic data")
}
return os.Rename(tmp, path)
}

View File

@ -74,6 +74,9 @@ func (w *writer) Write(p []byte) (n int, err error) {
}
func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error {
// Ensure even on error the writer is fully closed
defer unlock(w.ref)
var base content.Info
for _, opt := range opts {
if err := opt(&base); err != nil {
@ -81,8 +84,6 @@ func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest,
}
}
// Ensure even on error the writer is fully closed
defer unlock(w.ref)
fp := w.fp
w.fp = nil

View File

@ -20,7 +20,8 @@ package seccomp
import (
"runtime"
"syscall"
"golang.org/x/sys/unix"
"github.com/opencontainers/runtime-spec/specs-go"
)
@ -555,7 +556,7 @@ func DefaultProfile(sp *specs.Spec) *specs.LinuxSeccomp {
Args: []specs.LinuxSeccompArg{
{
Index: 1,
Value: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWUSER | syscall.CLONE_NEWPID | syscall.CLONE_NEWNET,
Value: unix.CLONE_NEWNS | unix.CLONE_NEWUTS | unix.CLONE_NEWIPC | unix.CLONE_NEWUSER | unix.CLONE_NEWPID | unix.CLONE_NEWNET | unix.CLONE_NEWCGROUP,
ValueTwo: 0,
Op: specs.OpMaskedEqual,
},
@ -570,7 +571,7 @@ func DefaultProfile(sp *specs.Spec) *specs.LinuxSeccomp {
Args: []specs.LinuxSeccompArg{
{
Index: 0,
Value: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWUSER | syscall.CLONE_NEWPID | syscall.CLONE_NEWNET,
Value: unix.CLONE_NEWNS | unix.CLONE_NEWUTS | unix.CLONE_NEWIPC | unix.CLONE_NEWUSER | unix.CLONE_NEWPID | unix.CLONE_NEWNET | unix.CLONE_NEWCGROUP,
ValueTwo: 0,
Op: specs.OpMaskedEqual,
},

View File

@ -23,10 +23,10 @@ const (
// DefaultMaxSendMsgSize defines the default maximum message size for
// sending protobufs passed over the GRPC API.
DefaultMaxSendMsgSize = 16 << 20
// DefaultRuntimeNSLabel defines the namespace label to check for
// DefaultRuntimeNSLabel defines the namespace label to check for the
// default runtime
DefaultRuntimeNSLabel = "containerd.io/defaults/runtime"
// DefaultSnapshotterNSLabel defines the namespances label to check for
// DefaultSnapshotterNSLabel defines the namespace label to check for the
// default snapshotter
DefaultSnapshotterNSLabel = "containerd.io/defaults/snapshotter"
)

View File

@ -45,9 +45,15 @@ type diffRemote struct {
client diffapi.DiffClient
}
func (r *diffRemote) Apply(ctx context.Context, diff ocispec.Descriptor, mounts []mount.Mount) (ocispec.Descriptor, error) {
func (r *diffRemote) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount, opts ...diff.ApplyOpt) (ocispec.Descriptor, error) {
var config diff.ApplyConfig
for _, opt := range opts {
if err := opt(&config); err != nil {
return ocispec.Descriptor{}, err
}
}
req := &diffapi.ApplyRequest{
Diff: fromDescriptor(diff),
Diff: fromDescriptor(desc),
Mounts: fromMounts(mounts),
}
resp, err := r.client.Apply(ctx, req)

View File

@ -53,7 +53,7 @@ var emptyDesc = ocispec.Descriptor{}
// Apply applies the content associated with the provided digests onto the
// provided mounts. Archive content will be extracted and decompressed if
// necessary.
func (s *fsApplier) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount) (d ocispec.Descriptor, err error) {
func (s *fsApplier) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount, opts ...diff.ApplyOpt) (d ocispec.Descriptor, err error) {
t1 := time.Now()
defer func() {
if err == nil {

View File

@ -51,6 +51,13 @@ type Comparer interface {
Compare(ctx context.Context, lower, upper []mount.Mount, opts ...Opt) (ocispec.Descriptor, error)
}
// ApplyConfig is used to hold parameters needed for a apply operation
type ApplyConfig struct {
}
// ApplyOpt is used to configure an Apply operation
type ApplyOpt func(*ApplyConfig) error
// Applier allows applying diffs between mounts
type Applier interface {
// Apply applies the content referred to by the given descriptor to
@ -58,7 +65,7 @@ type Applier interface {
// implementation and content descriptor. For example, in the common
// case the descriptor is a file system difference in tar format,
// that tar would be applied on top of the mounts.
Apply(ctx context.Context, desc ocispec.Descriptor, mount []mount.Mount) (ocispec.Descriptor, error)
Apply(ctx context.Context, desc ocispec.Descriptor, mount []mount.Mount, opts ...ApplyOpt) (ocispec.Descriptor, error)
}
// WithMediaType sets the media type to use for creating the diff, without

View File

@ -106,14 +106,15 @@ func (s *walkingDiff) Compare(ctx context.Context, lower, upper []mount.Mount, o
}
}()
if !newReference {
if err := cw.Truncate(0); err != nil {
if err = cw.Truncate(0); err != nil {
return err
}
}
if isCompressed {
dgstr := digest.SHA256.Digester()
compressed, err := compression.CompressStream(cw, compression.Gzip)
var compressed io.WriteCloser
compressed, err = compression.CompressStream(cw, compression.Gzip)
if err != nil {
return errors.Wrap(err, "failed to get compressed stream")
}

View File

@ -26,7 +26,11 @@
// client-side errors to the correct types.
package errdefs
import "github.com/pkg/errors"
import (
"context"
"github.com/pkg/errors"
)
// Definitions of common error types used throughout containerd. All containerd
// errors returned by most packages will map into one of these errors classes.
@ -76,3 +80,14 @@ func IsUnavailable(err error) bool {
func IsNotImplemented(err error) bool {
return errors.Cause(err) == ErrNotImplemented
}
// IsCanceled returns true if the error is due to `context.Canceled`.
func IsCanceled(err error) bool {
return errors.Cause(err) == context.Canceled
}
// IsDeadlineExceeded returns true if the error is due to
// `context.DeadlineExceeded`.
func IsDeadlineExceeded(err error) bool {
return errors.Cause(err) == context.DeadlineExceeded
}

View File

@ -17,6 +17,7 @@
package errdefs
import (
"context"
"strings"
"github.com/pkg/errors"
@ -55,6 +56,10 @@ func ToGRPC(err error) error {
return status.Errorf(codes.Unavailable, err.Error())
case IsNotImplemented(err):
return status.Errorf(codes.Unimplemented, err.Error())
case IsCanceled(err):
return status.Errorf(codes.Canceled, err.Error())
case IsDeadlineExceeded(err):
return status.Errorf(codes.DeadlineExceeded, err.Error())
}
return err
@ -89,6 +94,10 @@ func FromGRPC(err error) error {
cls = ErrFailedPrecondition
case codes.Unimplemented:
cls = ErrNotImplemented
case codes.Canceled:
cls = context.Canceled
case codes.DeadlineExceeded:
cls = context.DeadlineExceeded
default:
cls = ErrUnknown
}

View File

@ -20,26 +20,12 @@ import (
"context"
"io"
"github.com/containerd/containerd/images/oci"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/containerd/containerd/images/archive"
)
// 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, desc ocispec.Descriptor, opts ...oci.V1ExporterOpt) (io.ReadCloser, error) {
exporter, err := oci.ResolveV1ExportOpt(opts...)
if err != nil {
return nil, err
}
pr, pw := io.Pipe()
go func() {
pw.CloseWithError(errors.Wrap(exporter.Export(ctx, c.ContentStore(), desc, pw), "export failed"))
}()
return pr, nil
// Export exports images to a Tar stream.
// The tar archive is in OCI format with a Docker compatible manifest
// when a single target platform is given.
func (c *Client) Export(ctx context.Context, w io.Writer, opts ...archive.ExportOpt) error {
return archive.Export(ctx, c.ContentStore(), w, opts...)
}

View File

@ -0,0 +1,23 @@
/*
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 images
const (
// AnnotationImageName is an annotation on a Descriptor in an index.json
// containing the `Name` value as used by an `Image` struct
AnnotationImageName = "io.containerd.image.name"
)

View File

@ -0,0 +1,470 @@
/*
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 archive
import (
"archive/tar"
"context"
"encoding/json"
"io"
"path"
"sort"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/platforms"
digest "github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
type exportOptions struct {
manifests []ocispec.Descriptor
platform platforms.MatchComparer
allPlatforms bool
skipDockerManifest bool
}
// ExportOpt defines options for configuring exported descriptors
type ExportOpt func(context.Context, *exportOptions) error
// WithPlatform defines the platform to require manifest lists have
// not exporting all platforms.
// Additionally, platform is used to resolve image configs for
// Docker v1.1, v1.2 format compatibility.
func WithPlatform(p platforms.MatchComparer) ExportOpt {
return func(ctx context.Context, o *exportOptions) error {
o.platform = p
return nil
}
}
// WithAllPlatforms exports all manifests from a manifest list.
// Missing content will fail the export.
func WithAllPlatforms() ExportOpt {
return func(ctx context.Context, o *exportOptions) error {
o.allPlatforms = true
return nil
}
}
// WithSkipDockerManifest skips creation of the Docker compatible
// manifest.json file.
func WithSkipDockerManifest() ExportOpt {
return func(ctx context.Context, o *exportOptions) error {
o.skipDockerManifest = true
return nil
}
}
// WithImage adds the provided images to the exported archive.
func WithImage(is images.Store, name string) ExportOpt {
return func(ctx context.Context, o *exportOptions) error {
img, err := is.Get(ctx, name)
if err != nil {
return err
}
img.Target.Annotations = addNameAnnotation(name, img.Target.Annotations)
o.manifests = append(o.manifests, img.Target)
return nil
}
}
// WithManifest adds a manifest to the exported archive.
// It is up to caller to put name annotation to on the manifest
// descriptor if needed.
func WithManifest(manifest ocispec.Descriptor) ExportOpt {
return func(ctx context.Context, o *exportOptions) error {
o.manifests = append(o.manifests, manifest)
return nil
}
}
// WithNamedManifest adds a manifest to the exported archive
// with the provided names.
func WithNamedManifest(manifest ocispec.Descriptor, names ...string) ExportOpt {
return func(ctx context.Context, o *exportOptions) error {
for _, name := range names {
manifest.Annotations = addNameAnnotation(name, manifest.Annotations)
o.manifests = append(o.manifests, manifest)
}
return nil
}
}
func addNameAnnotation(name string, annotations map[string]string) map[string]string {
if annotations == nil {
annotations = map[string]string{}
}
annotations[images.AnnotationImageName] = name
annotations[ocispec.AnnotationRefName] = ociReferenceName(name)
return annotations
}
// Export implements Exporter.
func Export(ctx context.Context, store content.Provider, writer io.Writer, opts ...ExportOpt) error {
var eo exportOptions
for _, opt := range opts {
if err := opt(ctx, &eo); err != nil {
return err
}
}
records := []tarRecord{
ociLayoutFile(""),
ociIndexRecord(eo.manifests),
}
algorithms := map[string]struct{}{}
dManifests := map[digest.Digest]*exportManifest{}
resolvedIndex := map[digest.Digest]digest.Digest{}
for _, desc := range eo.manifests {
switch desc.MediaType {
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
mt, ok := dManifests[desc.Digest]
if !ok {
// TODO(containerd): Skip if already added
r, err := getRecords(ctx, store, desc, algorithms)
if err != nil {
return err
}
records = append(records, r...)
mt = &exportManifest{
manifest: desc,
}
dManifests[desc.Digest] = mt
}
name := desc.Annotations[images.AnnotationImageName]
if name != "" && !eo.skipDockerManifest {
mt.names = append(mt.names, name)
}
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
d, ok := resolvedIndex[desc.Digest]
if !ok {
records = append(records, blobRecord(store, desc))
p, err := content.ReadBlob(ctx, store, desc)
if err != nil {
return err
}
var index ocispec.Index
if err := json.Unmarshal(p, &index); err != nil {
return err
}
var manifests []ocispec.Descriptor
for _, m := range index.Manifests {
if eo.platform != nil {
if m.Platform == nil || eo.platform.Match(*m.Platform) {
manifests = append(manifests, m)
} else if !eo.allPlatforms {
continue
}
}
r, err := getRecords(ctx, store, m, algorithms)
if err != nil {
return err
}
records = append(records, r...)
}
if !eo.skipDockerManifest {
if len(manifests) >= 1 {
if len(manifests) > 1 {
sort.SliceStable(manifests, func(i, j int) bool {
if manifests[i].Platform == nil {
return false
}
if manifests[j].Platform == nil {
return true
}
return eo.platform.Less(*manifests[i].Platform, *manifests[j].Platform)
})
}
d = manifests[0].Digest
dManifests[d] = &exportManifest{
manifest: manifests[0],
}
} else if eo.platform != nil {
return errors.Wrap(errdefs.ErrNotFound, "no manifest found for platform")
}
}
resolvedIndex[desc.Digest] = d
}
if d != "" {
if name := desc.Annotations[images.AnnotationImageName]; name != "" {
mt := dManifests[d]
mt.names = append(mt.names, name)
}
}
default:
return errors.Wrap(errdefs.ErrInvalidArgument, "only manifests may be exported")
}
}
if len(dManifests) > 0 {
tr, err := manifestsRecord(ctx, store, dManifests)
if err != nil {
return errors.Wrap(err, "unable to create manifests file")
}
records = append(records, tr)
}
if len(algorithms) > 0 {
records = append(records, directoryRecord("blobs/", 0755))
for alg := range algorithms {
records = append(records, directoryRecord("blobs/"+alg+"/", 0755))
}
}
tw := tar.NewWriter(writer)
defer tw.Close()
return writeTar(ctx, tw, records)
}
func getRecords(ctx context.Context, store content.Provider, desc ocispec.Descriptor, algorithms map[string]struct{}) ([]tarRecord, error) {
var records []tarRecord
exportHandler := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
records = append(records, blobRecord(store, desc))
algorithms[desc.Digest.Algorithm().String()] = struct{}{}
return nil, nil
}
childrenHandler := images.ChildrenHandler(store)
handlers := images.Handlers(
childrenHandler,
images.HandlerFunc(exportHandler),
)
// Walk sequentially since the number of fetchs is likely one and doing in
// parallel requires locking the export handler
if err := images.Walk(ctx, handlers, desc); err != nil {
return nil, err
}
return records, nil
}
type tarRecord struct {
Header *tar.Header
CopyTo func(context.Context, io.Writer) (int64, error)
}
func blobRecord(cs content.Provider, desc ocispec.Descriptor) tarRecord {
path := path.Join("blobs", desc.Digest.Algorithm().String(), desc.Digest.Encoded())
return tarRecord{
Header: &tar.Header{
Name: path,
Mode: 0444,
Size: desc.Size,
Typeflag: tar.TypeReg,
},
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
r, err := cs.ReaderAt(ctx, desc)
if err != nil {
return 0, errors.Wrap(err, "failed to get reader")
}
defer r.Close()
// Verify digest
dgstr := desc.Digest.Algorithm().Digester()
n, err := io.Copy(io.MultiWriter(w, dgstr.Hash()), content.NewReader(r))
if err != nil {
return 0, errors.Wrap(err, "failed to copy to tar")
}
if dgstr.Digest() != desc.Digest {
return 0, errors.Errorf("unexpected digest %s copied", dgstr.Digest())
}
return n, nil
},
}
}
func directoryRecord(name string, mode int64) tarRecord {
return tarRecord{
Header: &tar.Header{
Name: name,
Mode: mode,
Typeflag: tar.TypeDir,
},
}
}
func ociLayoutFile(version string) tarRecord {
if version == "" {
version = ocispec.ImageLayoutVersion
}
layout := ocispec.ImageLayout{
Version: version,
}
b, err := json.Marshal(layout)
if err != nil {
panic(err)
}
return tarRecord{
Header: &tar.Header{
Name: ocispec.ImageLayoutFile,
Mode: 0444,
Size: int64(len(b)),
Typeflag: tar.TypeReg,
},
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
n, err := w.Write(b)
return int64(n), err
},
}
}
func ociIndexRecord(manifests []ocispec.Descriptor) tarRecord {
index := ocispec.Index{
Versioned: ocispecs.Versioned{
SchemaVersion: 2,
},
Manifests: manifests,
}
b, err := json.Marshal(index)
if err != nil {
panic(err)
}
return tarRecord{
Header: &tar.Header{
Name: "index.json",
Mode: 0644,
Size: int64(len(b)),
Typeflag: tar.TypeReg,
},
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
n, err := w.Write(b)
return int64(n), err
},
}
}
type exportManifest struct {
manifest ocispec.Descriptor
names []string
}
func manifestsRecord(ctx context.Context, store content.Provider, manifests map[digest.Digest]*exportManifest) (tarRecord, error) {
mfsts := make([]struct {
Config string
RepoTags []string
Layers []string
}, len(manifests))
var i int
for _, m := range manifests {
p, err := content.ReadBlob(ctx, store, m.manifest)
if err != nil {
return tarRecord{}, err
}
var manifest ocispec.Manifest
if err := json.Unmarshal(p, &manifest); err != nil {
return tarRecord{}, err
}
if err := manifest.Config.Digest.Validate(); err != nil {
return tarRecord{}, errors.Wrapf(err, "invalid manifest %q", m.manifest.Digest)
}
dgst := manifest.Config.Digest
mfsts[i].Config = path.Join("blobs", dgst.Algorithm().String(), dgst.Encoded())
for _, l := range manifest.Layers {
path := path.Join("blobs", l.Digest.Algorithm().String(), l.Digest.Encoded())
mfsts[i].Layers = append(mfsts[i].Layers, path)
}
for _, name := range m.names {
nname, err := familiarizeReference(name)
if err != nil {
return tarRecord{}, err
}
mfsts[i].RepoTags = append(mfsts[i].RepoTags, nname)
}
i++
}
b, err := json.Marshal(mfsts)
if err != nil {
return tarRecord{}, err
}
return tarRecord{
Header: &tar.Header{
Name: "manifest.json",
Mode: 0644,
Size: int64(len(b)),
Typeflag: tar.TypeReg,
},
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
n, err := w.Write(b)
return int64(n), err
},
}, nil
}
func writeTar(ctx context.Context, tw *tar.Writer, records []tarRecord) error {
sort.Slice(records, func(i, j int) bool {
return records[i].Header.Name < records[j].Header.Name
})
var last string
for _, record := range records {
if record.Header.Name == last {
continue
}
last = record.Header.Name
if err := tw.WriteHeader(record.Header); err != nil {
return err
}
if record.CopyTo != nil {
n, err := record.CopyTo(ctx, tw)
if err != nil {
return err
}
if n != record.Header.Size {
return errors.Errorf("unexpected copy size for %s", record.Header.Name)
}
} else if record.Header.Size > 0 {
return errors.Errorf("no content to write to record with non-zero size for %s", record.Header.Name)
}
}
return nil
}

View File

@ -181,7 +181,8 @@ func ImportIndex(ctx context.Context, store content.Store, reader io.Reader) (oc
}
mfstdesc.Annotations = map[string]string{
ocispec.AnnotationRefName: normalized,
images.AnnotationImageName: normalized,
ocispec.AnnotationRefName: ociReferenceName(normalized),
}
idx.Manifests = append(idx.Manifests, mfstdesc)
@ -197,10 +198,7 @@ func onUntarJSON(r io.Reader, j interface{}) error {
if err != nil {
return err
}
if err := json.Unmarshal(b, j); err != nil {
return err
}
return nil
return json.Unmarshal(b, j)
}
func onUntarBlob(ctx context.Context, r io.Reader, store content.Ingester, size int64, ref string) (digest.Digest, error) {

View File

@ -19,7 +19,8 @@ package archive
import (
"strings"
"github.com/docker/distribution/reference"
"github.com/containerd/containerd/reference"
distref "github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
@ -69,7 +70,7 @@ func isImagePrefix(s, prefix string) bool {
func normalizeReference(ref string) (string, error) {
// TODO: Replace this function to not depend on reference package
normalized, err := reference.ParseDockerRef(ref)
normalized, err := distref.ParseDockerRef(ref)
if err != nil {
return "", errors.Wrapf(err, "normalize image ref %q", ref)
}
@ -77,6 +78,31 @@ func normalizeReference(ref string) (string, error) {
return normalized.String(), nil
}
func familiarizeReference(ref string) (string, error) {
named, err := distref.ParseNormalizedNamed(ref)
if err != nil {
return "", errors.Wrapf(err, "failed to parse %q", ref)
}
named = distref.TagNameOnly(named)
return distref.FamiliarString(named), nil
}
func ociReferenceName(name string) string {
// OCI defines the reference name as only a tag excluding the
// repository. The containerd annotation contains the full image name
// since the tag is insufficent for correctly naming and referring to an
// image
var ociRef string
if spec, err := reference.Parse(name); err == nil {
ociRef = spec.Object
} else {
ociRef = name
}
return ociRef
}
// DigestTranslator creates a digest reference by adding the
// digest to an image name
func DigestTranslator(prefix string) func(digest.Digest) string {

View File

@ -1,241 +0,0 @@
/*
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 (
"archive/tar"
"context"
"encoding/json"
"io"
"sort"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/platforms"
ocispecs "github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
// V1Exporter implements OCI Image Spec v1.
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
//
// TODO(AkihiroSuda): add V1Exporter{TranslateMediaTypes: true} that transforms media types,
// e.g. application/vnd.docker.image.rootfs.diff.tar.gzip
// -> application/vnd.oci.image.layer.v1.tar+gzip
type V1Exporter struct {
AllPlatforms bool
}
// V1ExporterOpt allows the caller to set additional options to a new V1Exporter
type V1ExporterOpt func(c *V1Exporter) error
// DefaultV1Exporter return a default V1Exporter pointer
func DefaultV1Exporter() *V1Exporter {
return &V1Exporter{
AllPlatforms: false,
}
}
// ResolveV1ExportOpt return a new V1Exporter with V1ExporterOpt
func ResolveV1ExportOpt(opts ...V1ExporterOpt) (*V1Exporter, error) {
exporter := DefaultV1Exporter()
for _, o := range opts {
if err := o(exporter); err != nil {
return exporter, err
}
}
return exporter, nil
}
// WithAllPlatforms set V1Exporter`s AllPlatforms option
func WithAllPlatforms(allPlatforms bool) V1ExporterOpt {
return func(c *V1Exporter) error {
c.AllPlatforms = allPlatforms
return nil
}
}
// Export implements Exporter.
func (oe *V1Exporter) Export(ctx context.Context, store content.Provider, desc ocispec.Descriptor, writer io.Writer) error {
tw := tar.NewWriter(writer)
defer tw.Close()
records := []tarRecord{
ociLayoutFile(""),
ociIndexRecord(desc),
}
algorithms := map[string]struct{}{}
exportHandler := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
records = append(records, blobRecord(store, desc))
algorithms[desc.Digest.Algorithm().String()] = struct{}{}
return nil, nil
}
childrenHandler := images.ChildrenHandler(store)
if !oe.AllPlatforms {
// get local default platform to fetch image manifest
childrenHandler = images.FilterPlatforms(childrenHandler, platforms.Any(platforms.DefaultSpec()))
}
handlers := images.Handlers(
childrenHandler,
images.HandlerFunc(exportHandler),
)
// Walk sequentially since the number of fetchs is likely one and doing in
// parallel requires locking the export handler
if err := images.Walk(ctx, handlers, desc); err != nil {
return err
}
if len(algorithms) > 0 {
records = append(records, directoryRecord("blobs/", 0755))
for alg := range algorithms {
records = append(records, directoryRecord("blobs/"+alg+"/", 0755))
}
}
return writeTar(ctx, tw, records)
}
type tarRecord struct {
Header *tar.Header
CopyTo func(context.Context, io.Writer) (int64, error)
}
func blobRecord(cs content.Provider, desc ocispec.Descriptor) tarRecord {
path := "blobs/" + desc.Digest.Algorithm().String() + "/" + desc.Digest.Hex()
return tarRecord{
Header: &tar.Header{
Name: path,
Mode: 0444,
Size: desc.Size,
Typeflag: tar.TypeReg,
},
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
r, err := cs.ReaderAt(ctx, desc)
if err != nil {
return 0, errors.Wrap(err, "failed to get reader")
}
defer r.Close()
// Verify digest
dgstr := desc.Digest.Algorithm().Digester()
n, err := io.Copy(io.MultiWriter(w, dgstr.Hash()), content.NewReader(r))
if err != nil {
return 0, errors.Wrap(err, "failed to copy to tar")
}
if dgstr.Digest() != desc.Digest {
return 0, errors.Errorf("unexpected digest %s copied", dgstr.Digest())
}
return n, nil
},
}
}
func directoryRecord(name string, mode int64) tarRecord {
return tarRecord{
Header: &tar.Header{
Name: name,
Mode: mode,
Typeflag: tar.TypeDir,
},
}
}
func ociLayoutFile(version string) tarRecord {
if version == "" {
version = ocispec.ImageLayoutVersion
}
layout := ocispec.ImageLayout{
Version: version,
}
b, err := json.Marshal(layout)
if err != nil {
panic(err)
}
return tarRecord{
Header: &tar.Header{
Name: ocispec.ImageLayoutFile,
Mode: 0444,
Size: int64(len(b)),
Typeflag: tar.TypeReg,
},
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
n, err := w.Write(b)
return int64(n), err
},
}
}
func ociIndexRecord(manifests ...ocispec.Descriptor) tarRecord {
index := ocispec.Index{
Versioned: ocispecs.Versioned{
SchemaVersion: 2,
},
Manifests: manifests,
}
b, err := json.Marshal(index)
if err != nil {
panic(err)
}
return tarRecord{
Header: &tar.Header{
Name: "index.json",
Mode: 0644,
Size: int64(len(b)),
Typeflag: tar.TypeReg,
},
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
n, err := w.Write(b)
return int64(n), err
},
}
}
func writeTar(ctx context.Context, tw *tar.Writer, records []tarRecord) error {
sort.Slice(records, func(i, j int) bool {
return records[i].Header.Name < records[j].Header.Name
})
for _, record := range records {
if err := tw.WriteHeader(record.Header); err != nil {
return err
}
if record.CopyTo != nil {
n, err := record.CopyTo(ctx, tw)
if err != nil {
return err
}
if n != record.Header.Size {
return errors.Errorf("unexpected copy size for %s", record.Header.Name)
}
} else if record.Header.Size > 0 {
return errors.Errorf("no content to write to record with non-zero size for %s", record.Header.Name)
}
}
return nil
}

View File

@ -130,17 +130,13 @@ func (c *Client) Import(ctx context.Context, reader io.Reader, opts ...ImportOpt
}
for _, m := range idx.Manifests {
if ref := m.Annotations[ocispec.AnnotationRefName]; ref != "" {
if iopts.imageRefT != nil {
ref = iopts.imageRefT(ref)
}
if ref != "" {
name := imageName(m.Annotations, iopts.imageRefT)
if name != "" {
imgs = append(imgs, images.Image{
Name: ref,
Name: name,
Target: m,
})
}
}
if iopts.dgstRefT != nil {
ref := iopts.dgstRefT(m.Digest)
if ref != "" {
@ -178,3 +174,17 @@ func (c *Client) Import(ctx context.Context, reader io.Reader, opts ...ImportOpt
return imgs, nil
}
func imageName(annotations map[string]string, ociCleanup func(string) string) string {
name := annotations[images.AnnotationImageName]
if name != "" {
return name
}
name = annotations[ocispec.AnnotationRefName]
if name != "" {
if ociCleanup != nil {
name = ociCleanup(name)
}
}
return name
}

View File

@ -32,6 +32,9 @@ type Manager interface {
Create(context.Context, ...Opt) (Lease, error)
Delete(context.Context, Lease, ...DeleteOpt) error
List(context.Context, ...string) ([]Lease, error)
AddResource(context.Context, Lease, Resource) error
DeleteResource(context.Context, Lease, Resource) error
ListResources(context.Context, Lease) ([]Resource, error)
}
// Lease retains resources to prevent cleanup before
@ -42,6 +45,13 @@ type Lease struct {
Labels map[string]string
}
// Resource represents low level resource of image, like content, ingest and
// snapshotter.
type Resource struct {
ID string
Type string
}
// DeleteOptions provide options on image delete
type DeleteOptions struct {
Synchronous bool

View File

@ -91,3 +91,43 @@ func (pm *proxyManager) List(ctx context.Context, filters ...string) ([]leases.L
return l, nil
}
func (pm *proxyManager) AddResource(ctx context.Context, lease leases.Lease, r leases.Resource) error {
_, err := pm.client.AddResource(ctx, &leasesapi.AddResourceRequest{
ID: lease.ID,
Resource: leasesapi.Resource{
ID: r.ID,
Type: r.Type,
},
})
return errdefs.FromGRPC(err)
}
func (pm *proxyManager) DeleteResource(ctx context.Context, lease leases.Lease, r leases.Resource) error {
_, err := pm.client.DeleteResource(ctx, &leasesapi.DeleteResourceRequest{
ID: lease.ID,
Resource: leasesapi.Resource{
ID: r.ID,
Type: r.Type,
},
})
return errdefs.FromGRPC(err)
}
func (pm *proxyManager) ListResources(ctx context.Context, lease leases.Lease) ([]leases.Resource, error) {
resp, err := pm.client.ListResources(ctx, &leasesapi.ListResourcesRequest{
ID: lease.ID,
})
if err != nil {
return nil, errdefs.FromGRPC(err)
}
rs := make([]leases.Resource, 0, len(resp.Resources))
for _, i := range resp.Resources {
rs = append(rs, leases.Resource{
ID: i.ID,
Type: i.Type,
})
}
return rs, nil
}

View File

@ -567,6 +567,8 @@ func (nw *namespacedWriter) createAndCopy(ctx context.Context, desc ocispec.Desc
}
func (nw *namespacedWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error {
ctx = namespaces.WithNamespace(ctx, nw.namespace)
nw.l.RLock()
defer nw.l.RUnlock()
@ -767,11 +769,7 @@ func writeExpireAt(expire time.Time, bkt *bolt.Bucket) error {
if err != nil {
return err
}
if err := bkt.Put(bucketKeyExpireAt, expireAt); err != nil {
return err
}
return nil
return bkt.Put(bucketKeyExpireAt, expireAt)
}
func (cs *contentStore) garbageCollect(ctx context.Context) (d time.Duration, err error) {

View File

@ -18,6 +18,8 @@ package metadata
import (
"context"
"fmt"
"strings"
"time"
"github.com/containerd/containerd/errdefs"
@ -167,6 +169,128 @@ func (lm *LeaseManager) List(ctx context.Context, fs ...string) ([]leases.Lease,
return ll, nil
}
// AddResource references the resource by the provided lease.
func (lm *LeaseManager) AddResource(ctx context.Context, lease leases.Lease, r leases.Resource) error {
namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return err
}
topbkt := getBucket(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases, []byte(lease.ID))
if topbkt == nil {
return errors.Wrapf(errdefs.ErrNotFound, "lease %q", lease.ID)
}
keys, ref, err := parseLeaseResource(r)
if err != nil {
return err
}
bkt := topbkt
for _, key := range keys {
bkt, err = bkt.CreateBucketIfNotExists([]byte(key))
if err != nil {
return err
}
}
return bkt.Put([]byte(ref), nil)
}
// DeleteResource dereferences the resource by the provided lease.
func (lm *LeaseManager) DeleteResource(ctx context.Context, lease leases.Lease, r leases.Resource) error {
namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return err
}
topbkt := getBucket(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases, []byte(lease.ID))
if topbkt == nil {
return errors.Wrapf(errdefs.ErrNotFound, "lease %q", lease.ID)
}
keys, ref, err := parseLeaseResource(r)
if err != nil {
return err
}
bkt := topbkt
for _, key := range keys {
if bkt == nil {
break
}
bkt = bkt.Bucket([]byte(key))
}
if bkt == nil {
return nil
}
return bkt.Delete([]byte(ref))
}
// ListResources lists all the resources referenced by the lease.
func (lm *LeaseManager) ListResources(ctx context.Context, lease leases.Lease) ([]leases.Resource, error) {
namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return nil, err
}
topbkt := getBucket(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases, []byte(lease.ID))
if topbkt == nil {
return nil, errors.Wrapf(errdefs.ErrNotFound, "lease %q", lease.ID)
}
rs := make([]leases.Resource, 0)
// content resources
if cbkt := topbkt.Bucket(bucketKeyObjectContent); cbkt != nil {
if err := cbkt.ForEach(func(k, _ []byte) error {
rs = append(rs, leases.Resource{
ID: string(k),
Type: string(bucketKeyObjectContent),
})
return nil
}); err != nil {
return nil, err
}
}
// ingest resources
if lbkt := topbkt.Bucket(bucketKeyObjectIngests); lbkt != nil {
if err := lbkt.ForEach(func(k, _ []byte) error {
rs = append(rs, leases.Resource{
ID: string(k),
Type: string(bucketKeyObjectIngests),
})
return nil
}); err != nil {
return nil, err
}
}
// snapshot resources
if sbkt := topbkt.Bucket(bucketKeyObjectSnapshots); sbkt != nil {
if err := sbkt.ForEach(func(sk, sv []byte) error {
if sv != nil {
return nil
}
snbkt := sbkt.Bucket(sk)
return snbkt.ForEach(func(k, _ []byte) error {
rs = append(rs, leases.Resource{
ID: string(k),
Type: fmt.Sprintf("%s/%s", bucketKeyObjectSnapshots, sk),
})
return nil
})
}); err != nil {
return nil, err
}
}
return rs, nil
}
func addSnapshotLease(ctx context.Context, tx *bolt.Tx, snapshotter, key string) error {
lid, ok := leases.FromContext(ctx)
if !ok {
@ -307,3 +431,36 @@ func removeIngestLease(ctx context.Context, tx *bolt.Tx, ref string) error {
return bkt.Delete([]byte(ref))
}
func parseLeaseResource(r leases.Resource) ([]string, string, error) {
var (
ref = r.ID
typ = r.Type
keys = strings.Split(typ, "/")
)
switch k := keys[0]; k {
case string(bucketKeyObjectContent),
string(bucketKeyObjectIngests):
if len(keys) != 1 {
return nil, "", errors.Wrapf(errdefs.ErrInvalidArgument, "invalid resource type %s", typ)
}
if k == string(bucketKeyObjectContent) {
dgst, err := digest.Parse(ref)
if err != nil {
return nil, "", errors.Wrapf(errdefs.ErrInvalidArgument, "invalid content resource id %s: %v", ref, err)
}
ref = dgst.String()
}
case string(bucketKeyObjectSnapshots):
if len(keys) != 2 {
return nil, "", errors.Wrapf(errdefs.ErrInvalidArgument, "invalid snapshot resource type %s", typ)
}
default:
return nil, "", errors.Wrapf(errdefs.ErrNotImplemented, "resource type %s not supported yet", typ)
}
return keys, ref, nil
}

View File

@ -111,7 +111,18 @@ func unmount(target string, flags int) error {
// UnmountAll repeatedly unmounts the given mount point until there
// are no mounts remaining (EINVAL is returned by mount), which is
// useful for undoing a stack of mounts on the same mount point.
// UnmountAll all is noop when the first argument is an empty string.
// This is done when the containerd client did not specify any rootfs
// mounts (e.g. because the rootfs is managed outside containerd)
// UnmountAll is noop when the mount path does not exist.
func UnmountAll(mount string, flags int) error {
if mount == "" {
return nil
}
if _, err := os.Stat(mount); os.IsNotExist(err) {
return nil
}
for {
if err := unmount(mount, flags); err != nil {
// EINVAL is returned if the target is not a

View File

@ -36,10 +36,9 @@ type namespaceKey struct{}
// WithNamespace sets a given namespace on the context
func WithNamespace(ctx context.Context, namespace string) context.Context {
ctx = context.WithValue(ctx, namespaceKey{}, namespace) // set our key for namespace
// also store on the grpc headers so it gets picked up by any clients that
// also store on the grpc and ttrpc headers so it gets picked up by any clients that
// are using this.
return withGRPCNamespaceHeader(ctx, namespace)
return withTTRPCNamespaceHeader(withGRPCNamespaceHeader(ctx, namespace), namespace)
}
// NamespaceFromEnv uses the namespace defined in CONTAINERD_NAMESPACE or
@ -58,9 +57,10 @@ func NamespaceFromEnv(ctx context.Context) context.Context {
func Namespace(ctx context.Context) (string, bool) {
namespace, ok := ctx.Value(namespaceKey{}).(string)
if !ok {
return fromGRPCHeader(ctx)
if namespace, ok = fromGRPCHeader(ctx); !ok {
return fromTTRPCHeader(ctx)
}
}
return namespace, ok
}
@ -70,10 +70,8 @@ func NamespaceRequired(ctx context.Context) (string, error) {
if !ok || namespace == "" {
return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "namespace is required")
}
if err := Validate(namespace); err != nil {
return "", errors.Wrap(err, "namespace validation")
}
return namespace, nil
}

View File

@ -0,0 +1,41 @@
/*
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 namespaces
import (
"context"
"github.com/containerd/ttrpc"
)
const (
// TTRPCHeader defines the header name for specifying a containerd namespace
TTRPCHeader = "containerd-namespace-ttrpc"
)
func withTTRPCNamespaceHeader(ctx context.Context, namespace string) context.Context {
md, ok := ttrpc.GetMetadata(ctx)
if !ok {
md = ttrpc.Metadata{}
}
md.Set(TTRPCHeader, namespace)
return ttrpc.WithMetadata(ctx, md)
}
func fromTTRPCHeader(ctx context.Context) (string, bool) {
return ttrpc.GetMetadataValue(ctx, TTRPCHeader)
}

View File

@ -17,6 +17,7 @@
package dialer
import (
"context"
"net"
"time"
@ -28,8 +29,19 @@ type dialResult struct {
err error
}
// ContextDialer returns a GRPC net.Conn connected to the provided address
func ContextDialer(ctx context.Context, address string) (net.Conn, error) {
if deadline, ok := ctx.Deadline(); ok {
return timeoutDialer(address, time.Until(deadline))
}
return timeoutDialer(address, 0)
}
// Dialer returns a GRPC net.Conn connected to the provided address
func Dialer(address string, timeout time.Duration) (net.Conn, error) {
// Deprecated: use ContextDialer and grpc.WithContextDialer.
var Dialer = timeoutDialer
func timeoutDialer(address string, timeout time.Duration) (net.Conn, error) {
var (
stopC = make(chan struct{})
synC = make(chan *dialResult)

View File

@ -0,0 +1,105 @@
/*
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 ttrpcutil
import (
"sync"
"time"
v1 "github.com/containerd/containerd/api/services/ttrpc/events/v1"
"github.com/containerd/ttrpc"
"github.com/pkg/errors"
)
const ttrpcDialTimeout = 5 * time.Second
type ttrpcConnector func() (*ttrpc.Client, error)
// Client is the client to interact with TTRPC part of containerd server (plugins, events)
type Client struct {
mu sync.Mutex
connector ttrpcConnector
client *ttrpc.Client
closed bool
}
// NewClient returns a new containerd TTRPC client that is connected to the containerd instance provided by address
func NewClient(address string, opts ...ttrpc.ClientOpts) (*Client, error) {
connector := func() (*ttrpc.Client, error) {
conn, err := ttrpcDial(address, ttrpcDialTimeout)
if err != nil {
return nil, errors.Wrap(err, "failed to connect")
}
client := ttrpc.NewClient(conn, opts...)
return client, nil
}
client, err := connector()
if err != nil {
return nil, err
}
return &Client{
connector: connector,
client: client,
}, nil
}
// Reconnect re-establishes the TTRPC connection to the containerd daemon
func (c *Client) Reconnect() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.connector == nil {
return errors.New("unable to reconnect to containerd, no connector available")
}
if c.closed {
return errors.New("client is closed")
}
client, err := c.connector()
if err != nil {
return err
}
c.client = client
return nil
}
// EventsService creates an EventsService client
func (c *Client) EventsService() v1.EventsService {
return v1.NewEventsClient(c.Client())
}
// Client returns the underlying TTRPC client object
func (c *Client) Client() *ttrpc.Client {
c.mu.Lock()
defer c.mu.Unlock()
return c.client
}
// Close closes the clients TTRPC connection to containerd
func (c *Client) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
c.closed = true
return c.client.Close()
}

View File

@ -0,0 +1,30 @@
// +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 ttrpcutil
import (
"net"
"strings"
"time"
)
func ttrpcDial(address string, timeout time.Duration) (net.Conn, error) {
address = strings.TrimPrefix(address, "unix://")
return net.DialTimeout("unix", address, timeout)
}

View File

@ -0,0 +1,60 @@
// +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 ttrpcutil
import (
"net"
"os"
"time"
winio "github.com/Microsoft/go-winio"
"github.com/pkg/errors"
)
func ttrpcDial(address string, timeout time.Duration) (net.Conn, error) {
var c net.Conn
var lastError error
timedOutError := errors.Errorf("timed out waiting for npipe %s", address)
start := time.Now()
for {
remaining := timeout - time.Since(start)
if remaining <= 0 {
lastError = timedOutError
break
}
c, lastError = winio.DialPipe(address, &remaining)
if lastError == nil {
break
}
if !os.IsNotExist(lastError) {
break
}
// There is nobody serving the pipe. We limit the timeout for this case
// to 5 seconds because any shim that would serve this endpoint should
// serve it within 5 seconds. We use the passed in timeout for the
// `DialPipe` timeout if the pipe exists however to give the pipe time
// to `Accept` the connection.
if time.Since(start) >= 5*time.Second {
lastError = timedOutError
break
}
time.Sleep(10 * time.Millisecond)
}
return c, lastError
}

View File

@ -29,11 +29,48 @@ type MatchComparer interface {
// Only returns a match comparer for a single platform
// using default resolution logic for the platform.
//
// For ARMv8, will also match ARMv7, ARMv6 and ARMv5 (for 32bit runtimes)
// For ARMv7, will also match ARMv6 and ARMv5
// For ARMv6, will also match ARMv5
func Only(platform specs.Platform) MatchComparer {
platform = Normalize(platform)
if platform.Architecture == "arm" {
if platform.Variant == "v8" {
return orderedPlatformComparer{
matchers: []Matcher{
&matcher{
Platform: platform,
},
&matcher{
Platform: specs.Platform{
Architecture: platform.Architecture,
OS: platform.OS,
OSVersion: platform.OSVersion,
OSFeatures: platform.OSFeatures,
Variant: "v7",
},
},
&matcher{
Platform: specs.Platform{
Architecture: platform.Architecture,
OS: platform.OS,
OSVersion: platform.OSVersion,
OSFeatures: platform.OSFeatures,
Variant: "v6",
},
},
&matcher{
Platform: specs.Platform{
Architecture: platform.Architecture,
OS: platform.OS,
OSVersion: platform.OSVersion,
OSFeatures: platform.OSFeatures,
Variant: "v5",
},
},
},
}
}
if platform.Variant == "v7" {
return orderedPlatformComparer{
matchers: []Matcher{

View File

@ -97,7 +97,7 @@ func getCPUVariant() string {
}
switch variant {
case "8":
case "8", "AArch64":
variant = "v8"
case "7", "7M", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
variant = "v7"

View File

@ -30,7 +30,8 @@ var (
ErrNoType = errors.New("plugin: no type")
// ErrNoPluginID is returned when no id is specified
ErrNoPluginID = errors.New("plugin: no id")
// ErrIDRegistered is returned when a duplicate id is already registered
ErrIDRegistered = errors.New("plugin: id already registered")
// ErrSkipPlugin is used when a plugin is not initialized and should not be loaded,
// this allows the plugin loader differentiate between a plugin which is configured
// not to load and one that fails to load.
@ -100,6 +101,8 @@ type Registration struct {
// context are passed in. The init function may modify the registration to
// add exports, capabilities and platform support declarations.
InitFn func(*InitContext) (interface{}, error)
// Disable the plugin from loading
Disable bool
}
// Init the registered plugin
@ -157,12 +160,16 @@ func Load(path string) (err error) {
func Register(r *Registration) {
register.Lock()
defer register.Unlock()
if r.Type == "" {
panic(ErrNoType)
}
if r.ID == "" {
panic(ErrNoPluginID)
}
if err := checkUnique(r); err != nil {
panic(err)
}
var last bool
for _, requires := range r.Requires {
@ -177,24 +184,36 @@ func Register(r *Registration) {
register.r = append(register.r, r)
}
func checkUnique(r *Registration) error {
for _, registered := range register.r {
if r.URI() == registered.URI() {
return errors.Wrap(ErrIDRegistered, r.URI())
}
}
return nil
}
// DisableFilter filters out disabled plugins
type DisableFilter func(r *Registration) bool
// Graph returns an ordered list of registered plugins for initialization.
// Plugins in disableList specified by id will be disabled.
func Graph(disableList []string) (ordered []*Registration) {
func Graph(filter DisableFilter) (ordered []*Registration) {
register.RLock()
defer register.RUnlock()
for _, d := range disableList {
for i, r := range register.r {
if r.ID == d {
register.r = append(register.r[:i], register.r[i+1:]...)
break
}
for _, r := range register.r {
if filter(r) {
r.Disable = true
}
}
added := map[*Registration]bool{}
for _, r := range register.r {
children(r.ID, r.Requires, added, &ordered)
if r.Disable {
continue
}
children(r, added, &ordered)
if !added[r] {
ordered = append(ordered, r)
added[r] = true
@ -203,11 +222,13 @@ func Graph(disableList []string) (ordered []*Registration) {
return ordered
}
func children(id string, types []Type, added map[*Registration]bool, ordered *[]*Registration) {
for _, t := range types {
func children(reg *Registration, added map[*Registration]bool, ordered *[]*Registration) {
for _, t := range reg.Requires {
for _, r := range register.r {
if r.ID != id && (t == "*" || r.Type == t) {
children(r.ID, r.Requires, added, ordered)
if !r.Disable &&
r.URI() != reg.URI() &&
(t == "*" || r.Type == t) {
children(r, added, ordered)
if !added[r] {
*ordered = append(*ordered, r)
added[r] = true

View File

@ -88,7 +88,7 @@ func appendDistributionSourceLabel(originLabel, repo string) string {
}
repos = append(repos, repo)
// use emtpy string to present duplicate items
// use empty string to present duplicate items
for i := 1; i < len(repos); i++ {
tmp, j := repos[i], i-1
for ; j >= 0 && repos[j] >= tmp; j-- {

View File

@ -18,10 +18,10 @@ package docker
import (
"context"
"io"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"github.com/containerd/containerd/errdefs"
@ -29,6 +29,7 @@ import (
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/reference"
"github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/remotes/docker/schema1"
"github.com/containerd/containerd/version"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -150,6 +151,32 @@ func NewResolver(options ResolverOptions) remotes.Resolver {
}
}
func getManifestMediaType(resp *http.Response) string {
// Strip encoding data (manifests should always be ascii JSON)
contentType := resp.Header.Get("Content-Type")
if sp := strings.IndexByte(contentType, ';'); sp != -1 {
contentType = contentType[0:sp]
}
// As of Apr 30 2019 the registry.access.redhat.com registry does not specify
// the content type of any data but uses schema1 manifests.
if contentType == "text/plain" {
contentType = images.MediaTypeDockerSchema1Manifest
}
return contentType
}
type countingReader struct {
reader io.Reader
bytesRead int64
}
func (r *countingReader) Read(p []byte) (int, error) {
n, err := r.reader.Read(p)
r.bytesRead += int64(n)
return n, err
}
var _ remotes.Resolver = &dockerResolver{}
func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocispec.Descriptor, error) {
@ -220,40 +247,56 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
}
return "", ocispec.Descriptor{}, errors.Errorf("unexpected status code %v: %v", u, resp.Status)
}
size := resp.ContentLength
// this is the only point at which we trust the registry. we use the
// content headers to assemble a descriptor for the name. when this becomes
// more robust, we mostly get this information from a secure trust store.
dgstHeader := digest.Digest(resp.Header.Get("Docker-Content-Digest"))
contentType := getManifestMediaType(resp)
if dgstHeader != "" {
if dgstHeader != "" && size != -1 {
if err := dgstHeader.Validate(); err != nil {
return "", ocispec.Descriptor{}, errors.Wrapf(err, "%q in header not a valid digest", dgstHeader)
}
dgst = dgstHeader
}
} else {
log.G(ctx).Debug("no Docker-Content-Digest header, fetching manifest instead")
if dgst == "" {
return "", ocispec.Descriptor{}, errors.Errorf("could not resolve digest for %v", ref)
}
var (
size int64
sizeHeader = resp.Header.Get("Content-Length")
)
size, err = strconv.ParseInt(sizeHeader, 10, 64)
req, err := http.NewRequest(http.MethodGet, u, nil)
if err != nil {
return "", ocispec.Descriptor{}, errors.Wrapf(err, "invalid size header: %q", sizeHeader)
return "", ocispec.Descriptor{}, err
}
if size < 0 {
return "", ocispec.Descriptor{}, errors.Errorf("%q in header not a valid size", sizeHeader)
req.Header = r.headers
resp, err := fetcher.doRequestWithRetries(ctx, req, nil)
if err != nil {
return "", ocispec.Descriptor{}, err
}
defer resp.Body.Close()
bodyReader := countingReader{reader: resp.Body}
contentType = getManifestMediaType(resp)
if contentType == images.MediaTypeDockerSchema1Manifest {
b, err := schema1.ReadStripSignature(&bodyReader)
if err != nil {
return "", ocispec.Descriptor{}, err
}
dgst = digest.FromBytes(b)
} else {
dgst, err = digest.FromReader(&bodyReader)
if err != nil {
return "", ocispec.Descriptor{}, err
}
}
size = bodyReader.bytesRead
}
desc := ocispec.Descriptor{
Digest: dgst,
MediaType: resp.Header.Get("Content-Type"), // need to strip disposition?
MediaType: contentType,
Size: size,
}

View File

@ -227,6 +227,17 @@ func (c *Converter) Convert(ctx context.Context, opts ...ConvertOpt) (ocispec.De
return desc, nil
}
// ReadStripSignature reads in a schema1 manifest and returns a byte array
// with the "signatures" field stripped
func ReadStripSignature(schema1Blob io.Reader) ([]byte, error) {
b, err := ioutil.ReadAll(io.LimitReader(schema1Blob, manifestSizeLimit)) // limit to 8MB
if err != nil {
return nil, err
}
return stripSignature(b)
}
func (c *Converter) fetchManifest(ctx context.Context, desc ocispec.Descriptor) error {
log.G(ctx).Debug("fetch schema 1")
@ -235,17 +246,12 @@ func (c *Converter) fetchManifest(ctx context.Context, desc ocispec.Descriptor)
return err
}
b, err := ioutil.ReadAll(io.LimitReader(rc, manifestSizeLimit)) // limit to 8MB
b, err := ReadStripSignature(rc)
rc.Close()
if err != nil {
return err
}
b, err = stripSignature(b)
if err != nil {
return err
}
var m manifest
if err := json.Unmarshal(b, &m); err != nil {
return err

View File

@ -72,9 +72,9 @@ func (fn FetcherFunc) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.Re
// PusherFunc allows package users to implement a Pusher with just a
// function.
type PusherFunc func(ctx context.Context, desc ocispec.Descriptor, r io.Reader) error
type PusherFunc func(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error)
// Push content
func (fn PusherFunc) Push(ctx context.Context, desc ocispec.Descriptor, r io.Reader) error {
return fn(ctx, desc, r)
func (fn PusherFunc) Push(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error) {
return fn(ctx, desc)
}

View File

@ -65,7 +65,8 @@ func newBundle(id, path, workDir string, spec []byte) (b *bundle, err error) {
os.RemoveAll(workDir)
}
}()
if err := os.Mkdir(filepath.Join(path, "rootfs"), 0711); err != nil {
rootfs := filepath.Join(path, "rootfs")
if err := os.MkdirAll(rootfs, 0711); err != nil {
return nil, err
}
err = ioutil.WriteFile(filepath.Join(path, configFilename), spec, 0666)
@ -182,6 +183,9 @@ func atomicDelete(path string) error {
// create a hidden dir for an atomic removal
atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
if err := os.Rename(path, atomicPath); err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
return os.RemoveAll(atomicPath)

View File

@ -22,6 +22,7 @@ import (
"context"
"github.com/containerd/console"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/runtime/proc"
google_protobuf "github.com/gogo/protobuf/types"
"github.com/pkg/errors"
@ -55,11 +56,11 @@ func (s *deletedState) Start(ctx context.Context) error {
}
func (s *deletedState) Delete(ctx context.Context) error {
return errors.Errorf("cannot delete a deleted process")
return errors.Wrap(errdefs.ErrNotFound, "cannot delete a deleted process")
}
func (s *deletedState) Kill(ctx context.Context, sig uint32, all bool) error {
return errors.Errorf("cannot kill a deleted process")
return errors.Wrap(errdefs.ErrNotFound, "cannot kill a deleted process")
}
func (s *deletedState) SetExited(status int) {

View File

@ -324,13 +324,6 @@ func (p *Init) Resize(ws console.WinSize) error {
return p.console.Resize(ws)
}
func (p *Init) resize(ws console.WinSize) error {
if p.console == nil {
return nil
}
return p.console.Resize(ws)
}
// Pause the init process and all its child processes
func (p *Init) Pause(ctx context.Context) error {
p.mu.Lock()

View File

@ -21,7 +21,6 @@ package proc
import (
"context"
"github.com/containerd/console"
"github.com/containerd/containerd/runtime/proc"
runc "github.com/containerd/go-runc"
google_protobuf "github.com/gogo/protobuf/types"
@ -30,7 +29,6 @@ import (
)
type initState interface {
Resize(console.WinSize) error
Start(context.Context) error
Delete(context.Context) error
Pause(context.Context) error
@ -76,10 +74,6 @@ func (s *createdState) Checkpoint(ctx context.Context, r *CheckpointConfig) erro
return errors.Errorf("cannot checkpoint a task in created state")
}
func (s *createdState) Resize(ws console.WinSize) error {
return s.p.resize(ws)
}
func (s *createdState) Start(ctx context.Context) error {
if err := s.p.start(ctx); err != nil {
return err
@ -145,10 +139,6 @@ func (s *createdCheckpointState) Checkpoint(ctx context.Context, r *CheckpointCo
return errors.Errorf("cannot checkpoint a task in created state")
}
func (s *createdCheckpointState) Resize(ws console.WinSize) error {
return s.p.resize(ws)
}
func (s *createdCheckpointState) Start(ctx context.Context) error {
p := s.p
sio := p.stdio
@ -255,10 +245,6 @@ func (s *runningState) Checkpoint(ctx context.Context, r *CheckpointConfig) erro
return s.p.checkpoint(ctx, r)
}
func (s *runningState) Resize(ws console.WinSize) error {
return s.p.resize(ws)
}
func (s *runningState) Start(ctx context.Context) error {
return errors.Errorf("cannot start a running process")
}
@ -319,10 +305,6 @@ func (s *pausedState) Checkpoint(ctx context.Context, r *CheckpointConfig) error
return s.p.checkpoint(ctx, r)
}
func (s *pausedState) Resize(ws console.WinSize) error {
return s.p.resize(ws)
}
func (s *pausedState) Start(ctx context.Context) error {
return errors.Errorf("cannot start a paused process")
}
@ -381,10 +363,6 @@ func (s *stoppedState) Checkpoint(ctx context.Context, r *CheckpointConfig) erro
return errors.Errorf("cannot checkpoint a stopped container")
}
func (s *stoppedState) Resize(ws console.WinSize) error {
return errors.Errorf("cannot resize a stopped container")
}
func (s *stoppedState) Start(ctx context.Context) error {
return errors.Errorf("cannot start a stopped process")
}

View File

@ -271,27 +271,35 @@ func newBinaryIO(ctx context.Context, id string, uri *url.URL) (runc.IO, error)
)
out, err := newPipe()
if err != nil {
cancel()
return nil, err
}
serr, err := newPipe()
if err != nil {
cancel()
return nil, err
}
r, w, err := os.Pipe()
if err != nil {
cancel()
return nil, err
}
cmd.ExtraFiles = append(cmd.ExtraFiles, out.r, serr.r, w)
// don't need to register this with the reaper or wait when
// running inside a shim
if err := cmd.Start(); err != nil {
cancel()
return nil, err
}
// close our side of the pipe after start
w.Close()
if err := w.Close(); err != nil {
cancel()
return nil, err
}
// wait for the logging binary to be ready
b := make([]byte, 1)
if _, err := r.Read(b); err != nil && err != io.EOF {
cancel()
return nil, err
}
return &binaryIO{

View File

@ -56,6 +56,7 @@ func getLastRuntimeError(r *runc.Runc) (string, error) {
if err != nil {
return "", err
}
defer f.Close()
var (
errMsg string

View File

@ -191,18 +191,13 @@ func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts
}
exitHandler := func() {
log.G(ctx).WithField("id", id).Info("shim reaped")
t, err := r.tasks.Get(ctx, id)
if err != nil {
if _, err := r.tasks.Get(ctx, id); err != nil {
// Task was never started or was already successfully deleted
return
}
lc := t.(*Task)
log.G(ctx).WithFields(logrus.Fields{
"id": id,
"namespace": namespace,
}).Warn("cleaning up after killed shim")
if err = r.cleanupAfterDeadShim(context.Background(), bundle, namespace, id, lc.pid); err != nil {
if err = r.cleanupAfterDeadShim(context.Background(), bundle, namespace, id); err != nil {
log.G(ctx).WithError(err).WithFields(logrus.Fields{
"id": id,
"namespace": namespace,
@ -330,6 +325,10 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
continue
}
id := path.Name()
// skip hidden directories
if len(id) > 0 && id[0] == '.' {
continue
}
bundle := loadBundle(
id,
filepath.Join(r.state, ns, id),
@ -337,9 +336,15 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
)
ctx = namespaces.WithNamespace(ctx, ns)
pid, _ := runc.ReadPidFile(filepath.Join(bundle.path, proc.InitPidFile))
shimExit := make(chan struct{})
s, err := bundle.NewShimClient(ctx, ns, ShimConnect(r.config, func() {
err := r.cleanupAfterDeadShim(ctx, bundle, ns, id, pid)
if err != nil {
defer close(shimExit)
if _, err := r.tasks.Get(ctx, id); err != nil {
// Task was never started or was already successfully deleted
return
}
if err := r.cleanupAfterDeadShim(ctx, bundle, ns, id); err != nil {
log.G(ctx).WithError(err).WithField("bundle", bundle.path).
Error("cleaning up after dead shim")
}
@ -349,7 +354,7 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
"id": id,
"namespace": ns,
}).Error("connecting to shim")
err := r.cleanupAfterDeadShim(ctx, bundle, ns, id, pid)
err := r.cleanupAfterDeadShim(ctx, bundle, ns, id)
if err != nil {
log.G(ctx).WithError(err).WithField("bundle", bundle.path).
Error("cleaning up after dead shim")
@ -359,6 +364,18 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
logDirPath := filepath.Join(r.root, ns, id)
copyAndClose := func(dst io.Writer, src io.ReadWriteCloser) {
copyDone := make(chan struct{})
go func() {
io.Copy(dst, src)
close(copyDone)
}()
select {
case <-shimExit:
case <-copyDone:
}
src.Close()
}
shimStdoutLog, err := v1.OpenShimStdoutLog(ctx, logDirPath)
if err != nil {
log.G(ctx).WithError(err).WithFields(logrus.Fields{
@ -368,7 +385,11 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
}).Error("opening shim stdout log pipe")
continue
}
go io.Copy(os.Stdout, shimStdoutLog)
if r.config.ShimDebug {
go copyAndClose(os.Stdout, shimStdoutLog)
} else {
go copyAndClose(ioutil.Discard, shimStdoutLog)
}
shimStderrLog, err := v1.OpenShimStderrLog(ctx, logDirPath)
if err != nil {
@ -379,7 +400,11 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
}).Error("opening shim stderr log pipe")
continue
}
go io.Copy(os.Stderr, shimStderrLog)
if r.config.ShimDebug {
go copyAndClose(os.Stderr, shimStderrLog)
} else {
go copyAndClose(ioutil.Discard, shimStderrLog)
}
t, err := newTask(id, ns, pid, s, r.events, r.tasks, bundle)
if err != nil {
@ -391,7 +416,13 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
return o, nil
}
func (r *Runtime) cleanupAfterDeadShim(ctx context.Context, bundle *bundle, ns, id string, pid int) error {
func (r *Runtime) cleanupAfterDeadShim(ctx context.Context, bundle *bundle, ns, id string) error {
log.G(ctx).WithFields(logrus.Fields{
"id": id,
"namespace": ns,
}).Warn("cleaning up after shim dead")
pid, _ := runc.ReadPidFile(filepath.Join(bundle.path, proc.InitPidFile))
ctx = namespaces.WithNamespace(ctx, ns)
if err := r.terminate(ctx, bundle, ns, id); err != nil {
if r.config.ShimDebug {
@ -414,6 +445,10 @@ func (r *Runtime) cleanupAfterDeadShim(ctx context.Context, bundle *bundle, ns,
if err := bundle.Delete(); err != nil {
log.G(ctx).WithError(err).Error("delete bundle")
}
// kill shim
if shimPid, err := runc.ReadPidFile(filepath.Join(bundle.path, "shim.pid")); err == nil && shimPid > 0 {
unix.Kill(shimPid, unix.SIGKILL)
}
r.events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{
ContainerID: id,

View File

@ -87,7 +87,7 @@ func (t *Task) Namespace() string {
// Delete the task and return the exit status
func (t *Task) Delete(ctx context.Context) (*runtime.Exit, error) {
rsp, err := t.shim.Delete(ctx, empty)
if err != nil {
if err != nil && !errdefs.IsNotFound(err) {
return nil, errdefs.FromGRPC(err)
}
t.tasks.Delete(ctx, t.id)

View File

@ -26,6 +26,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
@ -98,9 +99,9 @@ func WithStart(binary, address, daemonAddress, cgroup string, debug bool, exitHa
cmd.Wait()
exitHandler()
if stdoutLog != nil {
stderrLog.Close()
stdoutLog.Close()
}
if stdoutLog != nil {
if stderrLog != nil {
stderrLog.Close()
}
}()
@ -110,7 +111,10 @@ func WithStart(binary, address, daemonAddress, cgroup string, debug bool, exitHa
"debug": debug,
}).Infof("shim %s started", binary)
if err := writeAddress(filepath.Join(config.Path, "address"), address); err != nil {
if err := writeFile(filepath.Join(config.Path, "address"), address); err != nil {
return nil, nil, err
}
if err := writeFile(filepath.Join(config.Path, "shim.pid"), strconv.Itoa(cmd.Process.Pid)); err != nil {
return nil, nil, err
}
// set shim in cgroup if it is provided
@ -172,8 +176,8 @@ func newCommand(binary, daemonAddress string, debug bool, config shim.Config, so
return cmd, nil
}
// writeAddress writes a address file atomically
func writeAddress(path, address string) error {
// writeFile writes a address file atomically
func writeFile(path, address string) error {
path, err := filepath.Abs(path)
if err != nil {
return err
@ -219,8 +223,7 @@ func WithConnect(address string, onClose func()) Opt {
if err != nil {
return nil, nil, err
}
client := ttrpc.NewClient(conn)
client.OnClose(onClose)
client := ttrpc.NewClient(conn, ttrpc.WithOnClose(onClose))
return shimapi.NewShimClient(client), conn, nil
}
}

View File

@ -124,6 +124,14 @@ func (s *Service) Create(ctx context.Context, r *shimapi.CreateTaskRequest) (_ *
})
}
rootfs := ""
if len(mounts) > 0 {
rootfs = filepath.Join(r.Bundle, "rootfs")
if err := os.Mkdir(rootfs, 0711); err != nil && !os.IsExist(err) {
return nil, err
}
}
config := &proc.CreateConfig{
ID: r.ID,
Bundle: r.Bundle,
@ -137,7 +145,6 @@ func (s *Service) Create(ctx context.Context, r *shimapi.CreateTaskRequest) (_ *
ParentCheckpoint: r.ParentCheckpoint,
Options: r.Options,
}
rootfs := filepath.Join(r.Bundle, "rootfs")
defer func() {
if err != nil {
if err2 := mount.UnmountAll(rootfs, 0); err2 != nil {
@ -169,6 +176,7 @@ func (s *Service) Create(ctx context.Context, r *shimapi.CreateTaskRequest) (_ *
s.config.SystemdCgroup,
s.platform,
config,
rootfs,
)
if err != nil {
return nil, errdefs.ToGRPC(err)
@ -546,7 +554,7 @@ func shouldKillAllOnExit(bundlePath string) (bool, error) {
if bundleSpec.Linux != nil {
for _, ns := range bundleSpec.Linux.Namespaces {
if ns.Type == specs.PIDNamespace {
if ns.Type == specs.PIDNamespace && ns.Path == "" {
return false, nil
}
}
@ -632,7 +640,7 @@ func getTopic(ctx context.Context, e interface{}) string {
return runtime.TaskUnknownTopic
}
func newInit(ctx context.Context, path, workDir, runtimeRoot, namespace, criu string, systemdCgroup bool, platform rproc.Platform, r *proc.CreateConfig) (*proc.Init, error) {
func newInit(ctx context.Context, path, workDir, runtimeRoot, namespace, criu string, systemdCgroup bool, platform rproc.Platform, r *proc.CreateConfig, rootfs string) (*proc.Init, error) {
var options runctypes.CreateOptions
if r.Options != nil {
v, err := typeurl.UnmarshalAny(r.Options)
@ -642,7 +650,6 @@ func newInit(ctx context.Context, path, workDir, runtimeRoot, namespace, criu st
options = *v.(*runctypes.CreateOptions)
}
rootfs := filepath.Join(path, "rootfs")
runtime := proc.NewRunc(runtimeRoot, path, namespace, r.Runtime, criu, systemdCgroup)
p := proc.New(r.ID, runtime, rproc.Stdio{
Stdin: r.Stdin,

View File

@ -30,6 +30,7 @@ import (
client "github.com/containerd/containerd/runtime/v2/shim"
"github.com/containerd/containerd/runtime/v2/task"
"github.com/containerd/ttrpc"
"github.com/gogo/protobuf/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@ -52,7 +53,7 @@ type binary struct {
rtTasks *runtime.TaskList
}
func (b *binary) Start(ctx context.Context) (_ *shim, err error) {
func (b *binary) Start(ctx context.Context, opts *types.Any, onClose func()) (_ *shim, err error) {
args := []string{"-id", b.bundle.ID}
if logrus.GetLevel() == logrus.DebugLevel {
args = append(args, "-debug")
@ -64,6 +65,7 @@ func (b *binary) Start(ctx context.Context) (_ *shim, err error) {
b.runtime,
b.containerdAddress,
b.bundle.Path,
opts,
args...,
)
if err != nil {
@ -84,8 +86,13 @@ func (b *binary) Start(ctx context.Context) (_ *shim, err error) {
go func() {
defer f.Close()
if _, err := io.Copy(os.Stderr, f); err != nil {
// When using a multi-container shim the 2nd to Nth container in the
// shim will not have a seperate log pipe. Ignore the failure log
// message here when the shim connect times out.
if !os.IsNotExist(errors.Cause(err)) {
log.G(ctx).WithError(err).Error("copy shim log")
}
}
}()
out, err := cmd.CombinedOutput()
if err != nil {
@ -96,8 +103,7 @@ func (b *binary) Start(ctx context.Context) (_ *shim, err error) {
if err != nil {
return nil, err
}
client := ttrpc.NewClient(conn)
client.OnClose(func() { conn.Close() })
client := ttrpc.NewClient(conn, ttrpc.WithOnClose(onClose))
return &shim{
bundle: b.bundle,
client: client,
@ -122,6 +128,7 @@ func (b *binary) Delete(ctx context.Context) (*runtime.Exit, error) {
b.runtime,
b.containerdAddress,
bundlePath,
nil,
"-id", b.bundle.ID,
"-bundle", b.bundle.Path,
"delete")
@ -148,9 +155,6 @@ func (b *binary) Delete(ctx context.Context) (*runtime.Exit, error) {
if err := b.bundle.Delete(); err != nil {
return nil, err
}
// remove self from the runtime task list
// this seems dirty but it cleans up the API across runtimes, tasks, and the service
b.rtTasks.Delete(ctx, b.bundle.ID)
return &runtime.Exit{
Status: response.ExitStatus,
Timestamp: response.ExitedAt,

View File

@ -24,6 +24,7 @@ import (
"path/filepath"
"github.com/containerd/containerd/identifiers"
"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/namespaces"
"github.com/pkg/errors"
)
@ -79,6 +80,11 @@ func NewBundle(ctx context.Context, root, state, id string, spec []byte) (b *Bun
if err := os.MkdirAll(filepath.Dir(work), 0711); err != nil {
return nil, err
}
rootfs := filepath.Join(b.Path, "rootfs")
if err := os.MkdirAll(rootfs, 0711); err != nil {
return nil, err
}
paths = append(paths, rootfs)
if err := os.Mkdir(work, 0711); err != nil {
if !os.IsExist(err) {
return nil, err
@ -89,10 +95,6 @@ func NewBundle(ctx context.Context, root, state, id string, spec []byte) (b *Bun
}
}
paths = append(paths, work)
// create rootfs dir
if err := os.Mkdir(filepath.Join(b.Path, "rootfs"), 0711); err != nil {
return nil, err
}
// symlink workdir
if err := os.Symlink(work, filepath.Join(b.Path, "work")); err != nil {
return nil, err
@ -115,6 +117,13 @@ type Bundle struct {
// Delete a bundle atomically
func (b *Bundle) Delete() error {
work, werr := os.Readlink(filepath.Join(b.Path, "work"))
rootfs := filepath.Join(b.Path, "rootfs")
if err := mount.UnmountAll(rootfs, 0); err != nil {
return errors.Wrapf(err, "unmount rootfs %s", rootfs)
}
if err := os.Remove(rootfs); err != nil && os.IsNotExist(err) {
return errors.Wrap(err, "failed to remove bundle rootfs")
}
err := atomicDelete(b.Path)
if err == nil {
if werr == nil {
@ -138,6 +147,9 @@ func atomicDelete(path string) error {
// create a hidden dir for an atomic removal
atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
if err := os.Rename(path, atomicPath); err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
return os.RemoveAll(atomicPath)

View File

@ -113,6 +113,10 @@ func (m *TaskManager) ID() string {
// Create a new task
func (m *TaskManager) Create(ctx context.Context, id string, opts runtime.CreateOpts) (_ runtime.Task, err error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return nil, err
}
bundle, err := NewBundle(ctx, m.root, m.state, id, opts.Spec.Value)
if err != nil {
return nil, err
@ -122,8 +126,21 @@ func (m *TaskManager) Create(ctx context.Context, id string, opts runtime.Create
bundle.Delete()
}
}()
topts := opts.TaskOptions
if topts == nil {
topts = opts.RuntimeOptions
}
b := shimBinary(ctx, bundle, opts.Runtime, m.containerdAddress, m.events, m.tasks)
shim, err := b.Start(ctx)
shim, err := b.Start(ctx, topts, func() {
log.G(ctx).WithField("id", id).Info("shim disconnected")
_, err := m.tasks.Get(ctx, id)
if err != nil {
// Task was never started or was already successfully deleted
return
}
cleanupAfterDeadShim(context.Background(), id, ns, m.events, b)
})
if err != nil {
return nil, err
}
@ -202,6 +219,10 @@ func (m *TaskManager) loadTasks(ctx context.Context) error {
continue
}
id := sd.Name()
// skip hidden directories
if len(id) > 0 && id[0] == '.' {
continue
}
bundle, err := LoadBundle(ctx, m.state, id)
if err != nil {
// fine to return error here, it is a programmer error if the context
@ -219,12 +240,9 @@ func (m *TaskManager) loadTasks(ctx context.Context) error {
bundle.Delete()
continue
}
shim, err := loadShim(ctx, bundle, m.events, m.tasks)
if err != nil {
log.G(ctx).WithError(err).Errorf("cleanup dead shim %s", id)
container, err := m.container(ctx, id)
if err != nil {
log.G(ctx).WithError(err).Errorf("loading dead container %s", id)
log.G(ctx).WithError(err).Errorf("loading container %s", id)
if err := mount.UnmountAll(filepath.Join(bundle.Path, "rootfs"), 0); err != nil {
log.G(ctx).WithError(err).Errorf("forceful unmount of rootfs %s", id)
}
@ -232,10 +250,17 @@ func (m *TaskManager) loadTasks(ctx context.Context) error {
continue
}
binaryCall := shimBinary(ctx, bundle, container.Runtime.Name, m.containerdAddress, m.events, m.tasks)
if _, err := binaryCall.Delete(ctx); err != nil {
log.G(ctx).WithError(err).Errorf("binary call to delete for %s", id)
continue
shim, err := loadShim(ctx, bundle, m.events, m.tasks, func() {
log.G(ctx).WithField("id", id).Info("shim disconnected")
_, err := m.tasks.Get(ctx, id)
if err != nil {
// Task was never started or was already successfully deleted
return
}
cleanupAfterDeadShim(context.Background(), id, ns, m.events, binaryCall)
})
if err != nil {
cleanupAfterDeadShim(ctx, id, ns, m.events, binaryCall)
continue
}
m.tasks.Add(ctx, shim)

View File

@ -24,18 +24,21 @@ import (
"path/filepath"
"time"
eventstypes "github.com/containerd/containerd/api/events"
"github.com/containerd/containerd/api/types"
tasktypes "github.com/containerd/containerd/api/types/task"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/events/exchange"
"github.com/containerd/containerd/identifiers"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/runtime"
client "github.com/containerd/containerd/runtime/v2/shim"
"github.com/containerd/containerd/runtime/v2/task"
"github.com/containerd/ttrpc"
ptypes "github.com/gogo/protobuf/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
func loadAddress(path string) (string, error) {
@ -46,7 +49,7 @@ func loadAddress(path string) (string, error) {
return string(data), nil
}
func loadShim(ctx context.Context, bundle *Bundle, events *exchange.Exchange, rt *runtime.TaskList) (_ *shim, err error) {
func loadShim(ctx context.Context, bundle *Bundle, events *exchange.Exchange, rt *runtime.TaskList, onClose func()) (_ *shim, err error) {
address, err := loadAddress(filepath.Join(bundle.Path, "address"))
if err != nil {
return nil, err
@ -55,6 +58,11 @@ func loadShim(ctx context.Context, bundle *Bundle, events *exchange.Exchange, rt
if err != nil {
return nil, err
}
defer func() {
if err != nil {
conn.Close()
}
}()
f, err := openShimLog(ctx, bundle)
if err != nil {
return nil, errors.Wrap(err, "open shim log pipe")
@ -70,12 +78,21 @@ func loadShim(ctx context.Context, bundle *Bundle, events *exchange.Exchange, rt
go func() {
defer f.Close()
if _, err := io.Copy(os.Stderr, f); err != nil {
// When using a multi-container shim the 2nd to Nth container in the
// shim will not have a seperate log pipe. Ignore the failure log
// message here when the shim connect times out.
if !os.IsNotExist(errors.Cause(err)) {
log.G(ctx).WithError(err).Error("copy shim log")
}
}
}()
client := ttrpc.NewClient(conn)
client.OnClose(func() { conn.Close() })
client := ttrpc.NewClient(conn, ttrpc.WithOnClose(onClose))
defer func() {
if err != nil {
client.Close()
}
}()
s := &shim{
client: client,
task: task.NewTaskClient(client),
@ -89,6 +106,52 @@ func loadShim(ctx context.Context, bundle *Bundle, events *exchange.Exchange, rt
return s, nil
}
func cleanupAfterDeadShim(ctx context.Context, id, ns string, events *exchange.Exchange, binaryCall *binary) {
ctx = namespaces.WithNamespace(ctx, ns)
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
log.G(ctx).WithFields(logrus.Fields{
"id": id,
"namespace": ns,
}).Warn("cleaning up after shim disconnected")
response, err := binaryCall.Delete(ctx)
if err != nil {
log.G(ctx).WithError(err).WithFields(logrus.Fields{
"id": id,
"namespace": ns,
}).Warn("failed to clean up after shim disconnected")
}
var (
pid uint32
exitStatus uint32
exitedAt time.Time
)
if response != nil {
pid = response.Pid
exitStatus = response.Status
exitedAt = response.Timestamp
} else {
exitStatus = 255
exitedAt = time.Now()
}
events.Publish(ctx, runtime.TaskExitEventTopic, &eventstypes.TaskExit{
ContainerID: id,
ID: id,
Pid: pid,
ExitStatus: exitStatus,
ExitedAt: exitedAt,
})
events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{
ContainerID: id,
Pid: pid,
ExitStatus: exitStatus,
ExitedAt: exitedAt,
})
}
type shim struct {
bundle *Bundle
client *ttrpc.Client
@ -120,19 +183,9 @@ func (s *shim) Shutdown(ctx context.Context) error {
}
func (s *shim) waitShutdown(ctx context.Context) error {
dead := make(chan struct{})
go func() {
if err := s.Shutdown(ctx); err != nil {
log.G(ctx).WithError(err).Error("shim shutdown error")
}
close(dead)
}()
select {
case <-time.After(3 * time.Second):
return errors.New("failed to shutdown shim in time")
case <-dead:
return nil
}
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
return s.Shutdown(ctx)
}
// ID of the shim/task
@ -152,18 +205,18 @@ func (s *shim) Delete(ctx context.Context) (*runtime.Exit, error) {
response, err := s.task.Delete(ctx, &task.DeleteRequest{
ID: s.ID(),
})
if err != nil {
if err != nil && !errdefs.IsNotFound(err) {
return nil, errdefs.FromGRPC(err)
}
if err := s.waitShutdown(ctx); err != nil {
return nil, err
}
if err := s.bundle.Delete(); err != nil {
return nil, err
}
// remove self from the runtime task list
// this seems dirty but it cleans up the API across runtimes, tasks, and the service
s.rtTasks.Delete(ctx, s.ID())
if err := s.waitShutdown(ctx); err != nil {
log.G(ctx).WithError(err).Error("failed to shutdown shim")
}
if err := s.bundle.Delete(); err != nil {
log.G(ctx).WithError(err).Error("failed to delete bundle")
}
return &runtime.Exit{
Status: response.ExitStatus,
Timestamp: response.ExitedAt,

View File

@ -1,87 +0,0 @@
/*
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 shim
import (
"net"
"sync"
v1 "github.com/containerd/containerd/api/services/ttrpc/events/v1"
"github.com/containerd/ttrpc"
"github.com/pkg/errors"
)
type dialConnect func() (net.Conn, error)
var errDialerClosed = errors.New("events dialer is closed")
func newDialier(newFn dialConnect) *dialer {
return &dialer{
newFn: newFn,
}
}
type dialer struct {
mu sync.Mutex
newFn dialConnect
service v1.EventsService
conn net.Conn
closed bool
}
func (d *dialer) Get() (v1.EventsService, error) {
d.mu.Lock()
defer d.mu.Unlock()
if d.closed {
return nil, errDialerClosed
}
if d.service == nil {
conn, err := d.newFn()
if err != nil {
return nil, err
}
d.conn = conn
d.service = v1.NewEventsClient(ttrpc.NewClient(conn))
}
return d.service, nil
}
func (d *dialer) Put(err error) {
if err != nil {
d.mu.Lock()
d.conn.Close()
d.service = nil
d.mu.Unlock()
}
}
func (d *dialer) Close() (err error) {
d.mu.Lock()
if d.closed {
return errDialerClosed
}
if d.conn != nil {
err = d.conn.Close()
}
d.service = nil
d.closed = true
d.mu.Unlock()
return err
}

View File

@ -18,13 +18,14 @@ package shim
import (
"context"
"net"
"sync"
"time"
v1 "github.com/containerd/containerd/api/services/ttrpc/events/v1"
"github.com/containerd/containerd/events"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/pkg/ttrpcutil"
"github.com/containerd/ttrpc"
"github.com/containerd/typeurl"
"github.com/sirupsen/logrus"
)
@ -40,20 +41,24 @@ type item struct {
count int
}
func newPublisher(address string) *remoteEventsPublisher {
func newPublisher(address string) (*remoteEventsPublisher, error) {
client, err := ttrpcutil.NewClient(address)
if err != nil {
return nil, err
}
l := &remoteEventsPublisher{
dialer: newDialier(func() (net.Conn, error) {
return connect(address, dial)
}),
client: client,
closed: make(chan struct{}),
requeue: make(chan *item, queueSize),
}
go l.processQueue()
return l
return l, nil
}
type remoteEventsPublisher struct {
dialer *dialer
client *ttrpcutil.Client
closed chan struct{}
closer sync.Once
requeue chan *item
@ -64,7 +69,7 @@ func (l *remoteEventsPublisher) Done() <-chan struct{} {
}
func (l *remoteEventsPublisher) Close() (err error) {
err = l.dialer.Close()
err = l.client.Close()
l.closer.Do(func() {
close(l.closed)
})
@ -79,18 +84,7 @@ func (l *remoteEventsPublisher) processQueue() {
continue
}
client, err := l.dialer.Get()
if err != nil {
l.dialer.Put(err)
l.queue(i)
logrus.WithError(err).Error("get events client")
continue
}
if _, err := client.Forward(i.ctx, &v1.ForwardRequest{
Envelope: i.ev,
}); err != nil {
l.dialer.Put(err)
if err := l.forwardRequest(i.ctx, &v1.ForwardRequest{Envelope: i.ev}); err != nil {
logrus.WithError(err).Error("forward event")
l.queue(i)
}
@ -124,22 +118,33 @@ func (l *remoteEventsPublisher) Publish(ctx context.Context, topic string, event
},
ctx: ctx,
}
client, err := l.dialer.Get()
if err != nil {
l.dialer.Put(err)
l.queue(i)
return err
}
if _, err := client.Forward(i.ctx, &v1.ForwardRequest{
Envelope: i.ev,
}); err != nil {
l.dialer.Put(err)
if err := l.forwardRequest(i.ctx, &v1.ForwardRequest{Envelope: i.ev}); err != nil {
l.queue(i)
return err
}
return nil
}
func connect(address string, d func(string, time.Duration) (net.Conn, error)) (net.Conn, error) {
return d(address, 5*time.Second)
func (l *remoteEventsPublisher) forwardRequest(ctx context.Context, req *v1.ForwardRequest) error {
_, err := l.client.EventsService().Forward(ctx, req)
if err == nil {
return nil
}
if err != ttrpc.ErrClosed {
return err
}
// Reconnect and retry request
if err := l.client.Reconnect(); err != nil {
return err
}
if _, err := l.client.EventsService().Forward(ctx, req); err != nil {
return err
}
return nil
}

View File

@ -78,6 +78,8 @@ type Config struct {
NoSubreaper bool
// NoReaper disables the shim binary from reaping any child process implicitly
NoReaper bool
// NoSetupLogger disables automatic configuration of logrus to use the shim FIFO
NoSetupLogger bool
}
var (
@ -160,9 +162,13 @@ func run(id string, initFunc Init, config Config) error {
return err
}
}
address := fmt.Sprintf("%s.ttrpc", addressFlag)
publisher := newPublisher(address)
address := fmt.Sprintf("%s.ttrpc", addressFlag)
publisher, err := newPublisher(address)
if err != nil {
return err
}
defer publisher.Close()
if namespaceFlag == "" {
@ -206,9 +212,11 @@ func run(id string, initFunc Init, config Config) error {
}
return nil
default:
if !config.NoSetupLogger {
if err := setLogger(ctx, idFlag); err != nil {
return err
}
}
client := NewShimClient(ctx, service, signals)
if err := client.Serve(); err != nil {
if err != context.Canceled {

View File

@ -24,9 +24,7 @@ import (
"net"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/containerd/fifo"
"github.com/pkg/errors"
@ -93,8 +91,3 @@ func handleSignals(ctx context.Context, logger *logrus.Entry, signals chan os.Si
func openLog(ctx context.Context, _ string) (io.Writer, error) {
return fifo.OpenFifo(ctx, "log", unix.O_WRONLY, 0700)
}
func dial(address string, timeout time.Duration) (net.Conn, error) {
address = strings.TrimPrefix(address, "unix://")
return net.DialTimeout("unix", address, timeout)
}

View File

@ -26,7 +26,6 @@ import (
"net"
"os"
"sync"
"time"
"unsafe"
winio "github.com/Microsoft/go-winio"
@ -286,35 +285,3 @@ func openLog(ctx context.Context, id string) (io.Writer, error) {
go dswl.beginAccept()
return dswl, nil
}
func dial(address string, timeout time.Duration) (net.Conn, error) {
var c net.Conn
var lastError error
timedOutError := errors.Errorf("timed out waiting for npipe %s", address)
start := time.Now()
for {
remaining := timeout - time.Since(start)
if remaining <= 0 {
lastError = timedOutError
break
}
c, lastError = winio.DialPipe(address, &remaining)
if lastError == nil {
break
}
if !os.IsNotExist(lastError) {
break
}
// There is nobody serving the pipe. We limit the timeout for this case
// to 5 seconds because any shim that would serve this endpoint should
// serve it within 5 seconds. We use the passed in timeout for the
// `DialPipe` timeout if the pipe exists however to give the pipe time
// to `Accept` the connection.
if time.Since(start) >= 5*time.Second {
lastError = timedOutError
break
}
time.Sleep(10 * time.Millisecond)
}
return c, lastError
}

View File

@ -17,6 +17,7 @@
package shim
import (
"bytes"
"context"
"fmt"
"io/ioutil"
@ -29,13 +30,15 @@ import (
"time"
"github.com/containerd/containerd/namespaces"
"github.com/gogo/protobuf/proto"
"github.com/gogo/protobuf/types"
"github.com/pkg/errors"
)
var runtimePaths sync.Map
// Command returns the shim command with the provided args and configuration
func Command(ctx context.Context, runtime, containerdAddress, path string, cmdArgs ...string) (*exec.Cmd, error) {
func Command(ctx context.Context, runtime, containerdAddress, path string, opts *types.Any, cmdArgs ...string) (*exec.Cmd, error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return nil, err
@ -94,6 +97,13 @@ func Command(ctx context.Context, runtime, containerdAddress, path string, cmdAr
cmd.Dir = path
cmd.Env = append(os.Environ(), "GOMAXPROCS=2")
cmd.SysProcAttr = getSysProcAttr()
if opts != nil {
d, err := proto.Marshal(opts)
if err != nil {
return nil, err
}
cmd.Stdin = bytes.NewReader(d)
}
return cmd, nil
}

View File

@ -28,5 +28,5 @@ import (
)
func openShimLog(ctx context.Context, bundle *Bundle) (io.ReadCloser, error) {
return fifo.OpenFifo(ctx, filepath.Join(bundle.Path, "log"), unix.O_RDWR|unix.O_CREAT, 0700)
return fifo.OpenFifo(ctx, filepath.Join(bundle.Path, "log"), unix.O_RDONLY|unix.O_CREAT|unix.O_NONBLOCK, 0700)
}

View File

@ -99,8 +99,10 @@ func (l *local) Apply(ctx context.Context, er *diffapi.ApplyRequest, _ ...grpc.C
mounts = toMounts(er.Mounts)
)
var opts []diff.ApplyOpt
for _, differ := range l.differs {
ocidesc, err = differ.Apply(ctx, desc, mounts)
ocidesc, err = differ.Apply(ctx, desc, mounts, opts...)
if !errdefs.IsNotImplemented(err) {
break
}

View File

@ -107,3 +107,27 @@ func (l *local) List(ctx context.Context, filters ...string) ([]leases.Lease, er
}
return ll, nil
}
func (l *local) AddResource(ctx context.Context, lease leases.Lease, r leases.Resource) error {
return l.db.Update(func(tx *bolt.Tx) error {
return metadata.NewLeaseManager(tx).AddResource(ctx, lease, r)
})
}
func (l *local) DeleteResource(ctx context.Context, lease leases.Lease, r leases.Resource) error {
return l.db.Update(func(tx *bolt.Tx) error {
return metadata.NewLeaseManager(tx).DeleteResource(ctx, lease, r)
})
}
func (l *local) ListResources(ctx context.Context, lease leases.Lease) ([]leases.Resource, error) {
var rs []leases.Resource
if err := l.db.View(func(tx *bolt.Tx) error {
var err error
rs, err = metadata.NewLeaseManager(tx).ListResources(ctx, lease)
return err
}); err != nil {
return nil, err
}
return rs, nil
}

View File

@ -113,6 +113,56 @@ func (s *service) List(ctx context.Context, r *api.ListRequest) (*api.ListRespon
}, nil
}
func (s *service) AddResource(ctx context.Context, r *api.AddResourceRequest) (*ptypes.Empty, error) {
lease := leases.Lease{
ID: r.ID,
}
if err := s.lm.AddResource(ctx, lease, leases.Resource{
ID: r.Resource.ID,
Type: r.Resource.Type,
}); err != nil {
return nil, errdefs.ToGRPC(err)
}
return &ptypes.Empty{}, nil
}
func (s *service) DeleteResource(ctx context.Context, r *api.DeleteResourceRequest) (*ptypes.Empty, error) {
lease := leases.Lease{
ID: r.ID,
}
if err := s.lm.DeleteResource(ctx, lease, leases.Resource{
ID: r.Resource.ID,
Type: r.Resource.Type,
}); err != nil {
return nil, errdefs.ToGRPC(err)
}
return &ptypes.Empty{}, nil
}
func (s *service) ListResources(ctx context.Context, r *api.ListResourcesRequest) (*api.ListResourcesResponse, error) {
lease := leases.Lease{
ID: r.ID,
}
rs, err := s.lm.ListResources(ctx, lease)
if err != nil {
return nil, errdefs.ToGRPC(err)
}
apiResources := make([]api.Resource, 0, len(rs))
for _, i := range rs {
apiResources = append(apiResources, api.Resource{
ID: i.ID,
Type: i.Type,
})
}
return &api.ListResourcesResponse{
Resources: apiResources,
}, nil
}
func leaseToGRPC(l leases.Lease) *api.Lease {
return &api.Lease{
ID: l.ID,

View File

@ -17,13 +17,18 @@
package config
import (
"strings"
"github.com/BurntSushi/toml"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/plugin"
"github.com/pkg/errors"
)
// Config provides containerd configuration data for the server
type Config struct {
// Version of the config file
Version int `toml:"version"`
// Root is the path to a directory where containerd will store persistent data
Root string `toml:"root"`
// State is the path to a directory where containerd will store transient data
@ -54,6 +59,42 @@ type Config struct {
md toml.MetaData
}
// GetVersion returns the config file's version
func (c *Config) GetVersion() int {
if c.Version == 0 {
return 1
}
return c.Version
}
// ValidateV2 validates the config for a v2 file
func (c *Config) ValidateV2() error {
if c.GetVersion() != 2 {
return nil
}
for _, p := range c.DisabledPlugins {
if len(strings.Split(p, ".")) < 4 {
return errors.Errorf("invalid disabled plugin URI %q expect io.containerd.x.vx", p)
}
}
for _, p := range c.RequiredPlugins {
if len(strings.Split(p, ".")) < 4 {
return errors.Errorf("invalid required plugin URI %q expect io.containerd.x.vx", p)
}
}
for p := range c.Plugins {
if len(strings.Split(p, ".")) < 4 {
return errors.Errorf("invalid plugin key URI %q expect io.containerd.x.vx", p)
}
}
for p := range c.ProxyPlugins {
if len(strings.Split(p, ".")) < 4 {
return errors.Errorf("invalid proxy plugin key URI %q expect io.containerd.x.vx", p)
}
}
return nil
}
// GRPCConfig provides GRPC configuration for the socket
type GRPCConfig struct {
Address string `toml:"address"`
@ -130,15 +171,19 @@ func (bc *BoltConfig) Validate() error {
}
// Decode unmarshals a plugin specific configuration by plugin id
func (c *Config) Decode(id string, v interface{}) (interface{}, error) {
func (c *Config) Decode(p *plugin.Registration) (interface{}, error) {
id := p.URI()
if c.GetVersion() == 1 {
id = p.ID
}
data, ok := c.Plugins[id]
if !ok {
return v, nil
return p.Config, nil
}
if err := c.md.PrimitiveDecode(data, v); err != nil {
if err := c.md.PrimitiveDecode(data, p.Config); err != nil {
return nil, err
}
return v, nil
return p.Config, nil
}
// LoadConfig loads the containerd server config from the provided path
@ -151,5 +196,29 @@ func LoadConfig(path string, v *Config) error {
return err
}
v.md = md
return nil
return v.ValidateV2()
}
// V1DisabledFilter matches based on ID
func V1DisabledFilter(list []string) plugin.DisableFilter {
set := make(map[string]struct{}, len(list))
for _, l := range list {
set[l] = struct{}{}
}
return func(r *plugin.Registration) bool {
_, ok := set[r.ID]
return ok
}
}
// V2DisabledFilter matches based on URI
func V2DisabledFilter(list []string) plugin.DisableFilter {
set := make(map[string]struct{}, len(list))
for _, l := range list {
set[l] = struct{}{}
}
return func(r *plugin.Registration) bool {
_, ok := set[r.URI()]
return ok
}
}

View File

@ -67,10 +67,8 @@ func CreateTopLevelDirectories(config *srvconfig.Config) error {
if err := sys.MkdirAllWithACL(config.Root, 0711); err != nil {
return err
}
if err := sys.MkdirAllWithACL(config.State, 0711); err != nil {
return err
}
return nil
return sys.MkdirAllWithACL(config.State, 0711)
}
// New creates and initializes a new containerd server
@ -128,6 +126,10 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
}
for _, p := range plugins {
id := p.URI()
reqID := id
if config.GetVersion() == 1 {
reqID = p.ID
}
log.G(ctx).WithField("type", p.Type).Infof("loading plugin %q...", id)
initContext := plugin.NewContext(
@ -142,11 +144,11 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
// load the plugin specific configuration if it is provided
if p.Config != nil {
pluginConfig, err := config.Decode(p.ID, p.Config)
pc, err := config.Decode(p)
if err != nil {
return nil, err
}
initContext.Config = pluginConfig
initContext.Config = pc
}
result := p.Init(initContext)
if err := initialized.Add(result); err != nil {
@ -160,12 +162,13 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
} else {
log.G(ctx).WithError(err).Warnf("failed to load plugin %s", id)
}
if _, ok := required[p.ID]; ok {
if _, ok := required[reqID]; ok {
return nil, errors.Wrapf(err, "load required plugin %s", id)
}
continue
}
delete(required, p.ID)
delete(required, reqID)
// check for grpc services that should be registered with the server
if src, ok := instance.(plugin.Service); ok {
grpcServices = append(grpcServices, src)
@ -268,7 +271,7 @@ func (s *Server) Stop() {
p := s.plugins[i]
instance, err := p.Instance()
if err != nil {
log.L.WithError(err).WithField("id", p.Registration.ID).
log.L.WithError(err).WithField("id", p.Registration.URI()).
Errorf("could not get plugin instance")
continue
}
@ -277,7 +280,7 @@ func (s *Server) Stop() {
continue
}
if err := closer.Close(); err != nil {
log.L.WithError(err).WithField("id", p.Registration.ID).
log.L.WithError(err).WithField("id", p.Registration.URI()).
Errorf("failed to close plugin")
}
}
@ -417,8 +420,12 @@ func LoadPlugins(ctx context.Context, config *srvconfig.Config) ([]*plugin.Regis
}
filter := srvconfig.V2DisabledFilter
if config.GetVersion() == 1 {
filter = srvconfig.V1DisabledFilter
}
// return the ordered graph for plugins
return plugin.Graph(config.DisabledPlugins), nil
return plugin.Graph(filter(config.DisabledPlugins)), nil
}
type proxyClients struct {
@ -438,7 +445,7 @@ func (pc *proxyClients) getClient(address string) (*grpc.ClientConn, error) {
gopts := []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithBackoffMaxDelay(3 * time.Second),
grpc.WithDialer(dialer.Dialer),
grpc.WithContextDialer(dialer.ContextDialer),
// TODO(stevvooe): We may need to allow configuration of this on the client.
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)),

View File

@ -19,7 +19,7 @@ package server
import (
"context"
srvconfig "github.com/containerd/containerd/server/config"
srvconfig "github.com/containerd/containerd/services/server/config"
)
func apply(_ context.Context, _ *srvconfig.Config) error {

View File

@ -77,3 +77,29 @@ func WithNoPivotRoot(_ context.Context, _ *Client, ti *TaskInfo) error {
}
return nil
}
// WithShimCgroup sets the existing cgroup for the shim
func WithShimCgroup(path string) NewTaskOpts {
return func(ctx context.Context, c *Client, ti *TaskInfo) error {
if CheckRuntime(ti.Runtime(), "io.containerd.runc") {
if ti.Options == nil {
ti.Options = &options.Options{}
}
opts, ok := ti.Options.(*options.Options)
if !ok {
return errors.New("invalid v2 shim create options format")
}
opts.ShimCgroup = path
} else {
if ti.Options == nil {
ti.Options = &runctypes.CreateOptions{}
}
opts, ok := ti.Options.(*runctypes.CreateOptions)
if !ok {
return errors.New("could not cast TaskInfo Options to CreateOptions")
}
opts.ShimCgroup = path
}
return nil
}
}

View File

@ -1,5 +1,5 @@
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f
github.com/containerd/cgroups 4994991857f9b0ae8dc439551e8bebdbb4bf66c1
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
@ -8,7 +8,7 @@ github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
github.com/docker/go-units v0.3.1
github.com/docker/go-units v0.4.0
github.com/godbus/dbus c7fdd8b5cd55e87b4e1f4e372cdb1db61dd6c66f
github.com/prometheus/client_golang f4fb1b73fb099f396a7f0036bf86aa8def4ed823
github.com/prometheus/client_model 99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c
@ -20,32 +20,32 @@ github.com/gogo/protobuf v1.2.1
github.com/gogo/googleapis v1.2.0
github.com/golang/protobuf v1.2.0
github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db
github.com/opencontainers/runc 029124da7af7360afa781a0234d1b083550f797c
github.com/opencontainers/runc v1.0.0-rc8
github.com/konsorten/go-windows-terminal-sequences v1.0.1
github.com/sirupsen/logrus v1.4.1
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
google.golang.org/grpc v1.12.0
golang.org/x/net f3200d17e092c607f615320ecaad13d87ad9a2b3
google.golang.org/grpc 25c4f928eaa6d96443009bd842389fb4fa48664e # v1.20.1
github.com/pkg/errors v0.8.1
github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7
golang.org/x/sys d455e41777fca6e8a5a79e34a14b8368bc11d9ba https://github.com/golang/sys
golang.org/x/sys 4c4f7f33c9ed00de01c4c741d2177abfcfe19307 https://github.com/golang/sys
github.com/opencontainers/image-spec v1.0.1
golang.org/x/sync 42b317875d0fa942474b76e1b46a6060d720ae6e
github.com/BurntSushi/toml v0.3.1
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
github.com/Microsoft/go-winio c599b533b43b1363d7d7c6cfda5ede70ed73ff13
github.com/Microsoft/go-winio 84b4ab48a50763fe7b3abcef38e5205c12027fac
github.com/Microsoft/hcsshim 8abdbb8205e4192c68b5f84c31197156f31be517
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
github.com/containerd/ttrpc f02858b1457c5ca3aaec3a0803eb0d59f96e41d6
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
github.com/containerd/ttrpc a5bd8ce9e40bc7c065a11c6936f4d032ce6bfa2b
github.com/syndtr/gocapability d98352740cb2c55f81556b63d4a1ec64c5a319c2
gotest.tools v2.3.0
github.com/google/go-cmp v0.2.0
go.etcd.io/bbolt v1.3.2
go.etcd.io/bbolt 2eb7227adea1d5cf85f0bc2a82b7059b13c2fa68
# cri dependencies
github.com/containerd/cri 6d353571e64417d80c9478ffaea793714dd539d0 # master
github.com/containerd/go-cni 40bcf8ec8acd7372be1d77031d585d5d8e561c90
github.com/containerd/cri 2fc62db8146ce66f27b37306ad5fda34207835f3 # master
github.com/containerd/go-cni 891c2a41e18144b2d7921f971d6c9789a68046b2
github.com/containernetworking/cni v0.6.0
github.com/containernetworking/plugins v0.7.0
github.com/davecgh/go-spew v1.1.0
@ -59,10 +59,10 @@ github.com/hashicorp/go-multierror ed905158d87462226a13fe39ddf685ea65f1c11f
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/selinux v1.2.1
github.com/opencontainers/selinux v1.2.2
github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0
github.com/tchap/go-patricia v2.2.6
golang.org/x/crypto 49796115aa4b964c318aad4f3084fdb41e9aa067
golang.org/x/crypto 88737f569e3a9c7ab309cdc09a07fe7fc87233c3
golang.org/x/oauth2 a6bd8cefa1811bd24b86f8902872e4e8225f74c4
golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
@ -78,8 +78,7 @@ sigs.k8s.io/yaml v1.1.0
# zfs dependencies
github.com/containerd/zfs 31af176f2ae84fe142ef2655bf7bb2aa618b3b1f
github.com/mistifyio/go-zfs d5b163290a48f624cbf244ebe4e89ce38653064c
github.com/pborman/uuid v1.2.0
github.com/mistifyio/go-zfs f784269be439d704d3dfa1906f45dd848fed2beb
github.com/google/uuid v1.1.1
# aufs dependencies

View File

@ -49,7 +49,15 @@ type Client struct {
err error
}
func NewClient(conn net.Conn) *Client {
type ClientOpts func(c *Client)
func WithOnClose(onClose func()) ClientOpts {
return func(c *Client) {
c.closeFunc = onClose
}
}
func NewClient(conn net.Conn, opts ...ClientOpts) *Client {
c := &Client{
codec: codec{},
conn: conn,
@ -60,6 +68,10 @@ func NewClient(conn net.Conn) *Client {
closeFunc: func() {},
}
for _, o := range opts {
o(c)
}
go c.run()
return c
}
@ -87,6 +99,10 @@ func (c *Client) Call(ctx context.Context, service, method string, req, resp int
cresp = &Response{}
)
if metadata, ok := GetMetadata(ctx); ok {
creq.Metadata = metadata
}
if dl, ok := ctx.Deadline(); ok {
creq.TimeoutNano = dl.Sub(time.Now()).Nanoseconds()
}
@ -141,11 +157,6 @@ func (c *Client) Close() error {
return nil
}
// OnClose allows a close func to be called when the server is closed
func (c *Client) OnClose(closer func()) {
c.closeFunc = closer
}
type message struct {
messageHeader
p []byte
@ -255,7 +266,7 @@ func (c *Client) recv(resp *Response, msg *message) error {
}
if msg.Type != messageTypeResponse {
return errors.New("unkown message type received")
return errors.New("unknown message type received")
}
defer c.channel.putmbuf(msg.p)

Some files were not shown because too many files have changed in this diff Show More