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

View File

@ -3,10 +3,13 @@
package winio package winio
import ( import (
"context"
"errors" "errors"
"fmt"
"io" "io"
"net" "net"
"os" "os"
"runtime"
"syscall" "syscall"
"time" "time"
"unsafe" "unsafe"
@ -18,6 +21,48 @@ import (
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo //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 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 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 ( const (
cERROR_PIPE_BUSY = syscall.Errno(231) cERROR_PIPE_BUSY = syscall.Errno(231)
@ -25,21 +70,20 @@ const (
cERROR_PIPE_CONNECTED = syscall.Errno(535) cERROR_PIPE_CONNECTED = syscall.Errno(535)
cERROR_SEM_TIMEOUT = syscall.Errno(121) cERROR_SEM_TIMEOUT = syscall.Errno(121)
cPIPE_ACCESS_DUPLEX = 0x3 cSECURITY_SQOS_PRESENT = 0x100000
cFILE_FLAG_FIRST_PIPE_INSTANCE = 0x80000 cSECURITY_ANONYMOUS = 0
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_TYPE_MESSAGE = 4
cPIPE_READMODE_MESSAGE = 2 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 ( var (
@ -137,9 +181,30 @@ func (s pipeAddress) String() string {
return string(s) 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 // 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 // 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) { func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
var absTimeout time.Time var absTimeout time.Time
if timeout != nil { if timeout != nil {
@ -147,23 +212,22 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
} else { } else {
absTimeout = time.Now().Add(time.Second * 2) absTimeout = time.Now().Add(time.Second * 2)
} }
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
conn, err := DialPipeContext(ctx, path)
if err == context.DeadlineExceeded {
return nil, ErrTimeout
}
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 err error
var h syscall.Handle var h syscall.Handle
for { h, err = tryDialPipe(ctx, &path)
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) {
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)
}
if err != nil { if err != nil {
return nil, &os.PathError{Op: "open", Path: path, Err: err} return nil, err
} }
var flags uint32 var flags uint32
@ -194,43 +258,87 @@ type acceptResponse struct {
} }
type win32PipeListener struct { type win32PipeListener struct {
firstHandle syscall.Handle firstHandle syscall.Handle
path string path string
securityDescriptor []byte config PipeConfig
config PipeConfig acceptCh chan (chan acceptResponse)
acceptCh chan (chan acceptResponse) closeCh chan int
closeCh chan int doneCh chan int
doneCh chan int
} }
func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, first bool) (syscall.Handle, error) { func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED path16, err := syscall.UTF16FromString(path)
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)
if err != nil { if err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err} 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 return h, nil
} }
func (l *win32PipeListener) makeServerPipe() (*win32File, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -341,32 +449,13 @@ func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
if err != nil { if err != nil {
return nil, err 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{ l := &win32PipeListener{
firstHandle: h, firstHandle: h,
path: path, path: path,
securityDescriptor: sd, config: *c,
config: *c, acceptCh: make(chan (chan acceptResponse)),
acceptCh: make(chan (chan acceptResponse)), closeCh: make(chan int),
closeCh: make(chan int), doneCh: make(chan int),
doneCh: make(chan int),
} }
go l.listenerRoutine() go l.listenerRoutine()
return l, nil return l, nil

View File

@ -17,7 +17,7 @@ const (
// will always be collected. // will always be collected.
type Level uint8 type Level uint8
// Predefined ETW log levels. // Predefined ETW log levels from winmeta.xml in the Windows SDK.
const ( const (
LevelAlways Level = iota LevelAlways Level = iota
LevelCritical LevelCritical
@ -27,13 +27,30 @@ const (
LevelVerbose 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. // EventDescriptor represents various metadata for an ETW event.
type eventDescriptor struct { type eventDescriptor struct {
id uint16 id uint16
version uint8 version uint8
channel Channel channel Channel
level Level level Level
opcode uint8 opcode Opcode
task uint16 task uint16
keyword uint64 keyword uint64
} }

View File

@ -1,13 +1,13 @@
package etw package etw
import ( import (
"golang.org/x/sys/windows" "github.com/Microsoft/go-winio/pkg/guid"
) )
type eventOptions struct { type eventOptions struct {
descriptor *eventDescriptor descriptor *eventDescriptor
activityID *windows.GUID activityID *guid.GUID
relatedActivityID *windows.GUID relatedActivityID *guid.GUID
tags uint32 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 { func WithChannel(channel Channel) EventOpt {
return func(options *eventOptions) { return func(options *eventOptions) {
options.descriptor.channel = channel 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 // 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. // value (top 4 bits are ignored) which are interpreted by the event consumer.
func WithTags(newTags uint32) EventOpt { 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) { return func(options *eventOptions) {
options.activityID = activityID 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) { return func(options *eventOptions) {
options.relatedActivityID = activityID options.relatedActivityID = activityID
} }

View File

@ -1,7 +1,9 @@
package etw package etw
import ( import (
"fmt"
"math" "math"
"reflect"
"unsafe" "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" "bytes"
"crypto/sha1" "crypto/sha1"
"encoding/binary" "encoding/binary"
"encoding/hex"
"fmt"
"strings" "strings"
"unicode/utf16" "unicode/utf16"
"unsafe" "unsafe"
"github.com/Microsoft/go-winio/pkg/guid"
"golang.org/x/sys/windows" "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 // 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). // (e.g. don't use multiple provider names with the same ID, or vice versa).
type Provider struct { type Provider struct {
ID *windows.GUID ID *guid.GUID
handle providerHandle handle providerHandle
metadata []byte metadata []byte
callback EnableCallback callback EnableCallback
@ -30,19 +29,7 @@ type Provider struct {
// String returns the `provider`.ID as a string // String returns the `provider`.ID as a string
func (provider *Provider) String() string { func (provider *Provider) String() string {
data1 := make([]byte, 4) return provider.ID.String()
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:]))
} }
type providerHandle windows.Handle type providerHandle windows.Handle
@ -72,9 +59,9 @@ const (
// EnableCallback is the form of the callback function that receives provider // EnableCallback is the form of the callback function that receives provider
// enable/disable notifications from ETW. // 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)) provider := providers.getProvider(uint(i))
switch state { 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 // for provider notifications. Because Go has trouble with callback arguments of
// different size, it has only pointer-sized arguments, which are then cast to // different size, it has only pointer-sized arguments, which are then cast to
// the appropriate types when calling providerCallback. // 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) providerCallback(sourceID, ProviderState(state), Level(level), uint64(matchAnyKeyword), uint64(matchAllKeyword), filterData, i)
return 0 return 0
} }
@ -108,7 +95,7 @@ func providerCallbackAdapter(sourceID *windows.GUID, state uintptr, level uintpt
// The algorithm is roughly: // The algorithm is roughly:
// Hash = Sha1(namespace + arg.ToUpper().ToUtf16be()) // Hash = Sha1(namespace + arg.ToUpper().ToUtf16be())
// Guid = Hash[0..15], with Hash[7] tweaked according to RFC 4122 // 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() buffer := sha1.New()
namespace := []byte{0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8, 0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB} 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 := buffer.Sum(nil)
sum[7] = (sum[7] & 0xf) | 0x50 sum[7] = (sum[7] & 0xf) | 0x50
return &windows.GUID{ return &guid.GUID{
Data1: binary.LittleEndian.Uint32(sum[0:4]), Data1: binary.LittleEndian.Uint32(sum[0:4]),
Data2: binary.LittleEndian.Uint16(sum[4:6]), Data2: binary.LittleEndian.Uint16(sum[4:6]),
Data3: binary.LittleEndian.Uint16(sum[6:8]), 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 // 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 // existing provider ID that must be used to conform to existing diagnostic
// infrastructure. // 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() { providerCallbackOnce.Do(func() {
globalProviderCallback = windows.NewCallback(providerCallbackAdapter) globalProviderCallback = windows.NewCallback(providerCallbackAdapter)
}) })
@ -151,7 +138,7 @@ func NewProviderWithID(name string, id *windows.GUID, callback EnableCallback) (
provider.ID = id provider.ID = id
provider.callback = callback 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 return nil, err
} }
@ -247,7 +234,7 @@ func (provider *Provider) WriteEvent(name string, eventOpts []EventOpt, fieldOpt
dataBlobs = [][]byte{ed.bytes()} 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 // 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. // the ETW infrastructure.
func (provider *Provider) writeEventRaw( func (provider *Provider) writeEventRaw(
descriptor *eventDescriptor, descriptor *eventDescriptor,
activityID *windows.GUID, activityID *guid.GUID,
relatedActivityID *windows.GUID, relatedActivityID *guid.GUID,
metadataBlobs [][]byte, metadataBlobs [][]byte,
dataBlobs [][]byte) error { dataBlobs [][]byte) error {
@ -275,5 +262,5 @@ func (provider *Provider) writeEventRaw(
dataDescriptors = append(dataDescriptors, newEventDataDescriptor(eventDataDescriptorTypeUserData, blob)) 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 package etwlogrus
import ( import (
"fmt"
"reflect"
"github.com/Microsoft/go-winio/pkg/etw" "github.com/Microsoft/go-winio/pkg/etw"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -71,7 +68,7 @@ func (h *Hook) Fire(e *logrus.Entry) error {
fields = append(fields, etw.StringField("Message", e.Message)) fields = append(fields, etw.StringField("Message", e.Message))
for k, v := range e.Data { for k, v := range e.Data {
fields = append(fields, getFieldOpt(k, v)) fields = append(fields, etw.SmartField(k, v))
} }
return h.provider.WriteEvent( return h.provider.WriteEvent(
@ -80,127 +77,6 @@ func (h *Hook) Fire(e *logrus.Entry) error {
fields) 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 // 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 // registered by etwlogrus, it will be closed as part of `Close`. If the
// provider was passed in, it will not be closed. // 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 package winio
@ -38,6 +38,7 @@ func errnoErr(e syscall.Errno) error {
var ( var (
modkernel32 = windows.NewLazySystemDLL("kernel32.dll") modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
modntdll = windows.NewLazySystemDLL("ntdll.dll")
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
procCancelIoEx = modkernel32.NewProc("CancelIoEx") procCancelIoEx = modkernel32.NewProc("CancelIoEx")
@ -47,10 +48,13 @@ var (
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procCreateFileW = modkernel32.NewProc("CreateFileW") procCreateFileW = modkernel32.NewProc("CreateFileW")
procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW")
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo") procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW") procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
procLocalAlloc = modkernel32.NewProc("LocalAlloc") 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") procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW") procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW") procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
@ -176,27 +180,6 @@ func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityA
return 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) { 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) 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 { if r1 == 0 {
@ -227,6 +210,32 @@ func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
return 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) { func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
var _p0 *uint16 var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(accountName) _p0, err = syscall.UTF16PtrFromString(accountName)

View File

@ -1,6 +1,7 @@
Apache License Apache License
Version 2.0, January 2004 Version 2.0, January 2004
http://www.apache.org/licenses/ https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
@ -175,24 +176,13 @@
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work. Copyright The containerd Authors
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]
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at 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 Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, 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() ws, err := current.Size()
current.Resize(ws) 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) [![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) [![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 // List lists all active leases, returning the full list of
// leases and optionally including the referenced resources. // leases and optionally including the referenced resources.
rpc List(ListRequest) returns (ListResponse); 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. // Lease is an object which retains resources while it exists.
@ -62,3 +71,32 @@ message ListRequest {
message ListResponse { message ListResponse {
repeated Lease leases = 1; 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.WithInsecure(),
grpc.FailOnNonTempDialError(true), grpc.FailOnNonTempDialError(true),
grpc.WithBackoffMaxDelay(3 * time.Second), 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. // TODO(stevvooe): We may need to allow configuration of this on the client.
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)), 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") return false, errors.New("no grpc connection available")
} }
c.connMu.Unlock() 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 { if err != nil {
return false, err return false, err
} }
@ -622,6 +622,13 @@ func (c *Client) VersionService() versionservice.VersionClient {
return versionservice.NewVersionClient(c.conn) 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 // Version of containerd
type Version struct { type Version struct {
// Version number // Version number

View File

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

View File

@ -23,8 +23,9 @@ import (
func defaultConfig() *srvconfig.Config { func defaultConfig() *srvconfig.Config {
return &srvconfig.Config{ return &srvconfig.Config{
Root: defaults.DefaultRootDir, Version: 2,
State: defaults.DefaultStateDir, Root: defaults.DefaultRootDir,
State: defaults.DefaultStateDir,
GRPC: srvconfig.GRPCConfig{ GRPC: srvconfig.GRPCConfig{
Address: defaults.DefaultAddress, Address: defaults.DefaultAddress,
MaxRecvMsgSize: defaults.DefaultMaxRecvMsgSize, MaxRecvMsgSize: defaults.DefaultMaxRecvMsgSize,

View File

@ -25,8 +25,9 @@ import (
func defaultConfig() *srvconfig.Config { func defaultConfig() *srvconfig.Config {
return &srvconfig.Config{ return &srvconfig.Config{
Root: defaults.DefaultRootDir, Version: 2,
State: defaults.DefaultStateDir, Root: defaults.DefaultRootDir,
State: defaults.DefaultStateDir,
GRPC: srvconfig.GRPCConfig{ GRPC: srvconfig.GRPCConfig{
Address: defaults.DefaultAddress, Address: defaults.DefaultAddress,
}, },

View File

@ -23,8 +23,9 @@ import (
func defaultConfig() *srvconfig.Config { func defaultConfig() *srvconfig.Config {
return &srvconfig.Config{ return &srvconfig.Config{
Root: defaults.DefaultRootDir, Version: 2,
State: defaults.DefaultStateDir, Root: defaults.DefaultRootDir,
State: defaults.DefaultStateDir,
GRPC: srvconfig.GRPCConfig{ GRPC: srvconfig.GRPCConfig{
Address: defaults.DefaultAddress, Address: defaults.DefaultAddress,
MaxRecvMsgSize: defaults.DefaultMaxRecvMsgSize, MaxRecvMsgSize: defaults.DefaultMaxRecvMsgSize,

View File

@ -278,7 +278,7 @@ func setLevel(context *cli.Context, config *srvconfig.Config) error {
return nil return nil
} }
func dumpStacks() { func dumpStacks(writeToFile bool) {
var ( var (
buf []byte buf []byte
stackSize int stackSize int
@ -291,4 +291,16 @@ func dumpStacks() {
} }
buf = buf[:stackSize] buf = buf[:stackSize]
logrus.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf) 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") log.G(ctx).WithField("signal", s).Debug("received signal")
switch s { switch s {
case unix.SIGUSR1: case unix.SIGUSR1:
dumpStacks() dumpStacks(true)
case unix.SIGPIPE: case unix.SIGPIPE:
continue continue
default: default:

View File

@ -24,7 +24,9 @@ import (
"unsafe" "unsafe"
winio "github.com/Microsoft/go-winio" 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/etwlogrus"
"github.com/Microsoft/go-winio/pkg/guid"
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
"github.com/containerd/containerd/services/server" "github.com/containerd/containerd/services/server"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -86,16 +88,30 @@ func setupDumpStacks() {
logrus.Debugf("Stackdump - waiting signal at %s", event) logrus.Debugf("Stackdump - waiting signal at %s", event)
for { for {
windows.WaitForSingleObject(h, windows.INFINITE) windows.WaitForSingleObject(h, windows.INFINITE)
dumpStacks() dumpStacks(true)
} }
}() }()
} }
func init() { func etwCallback(sourceID *guid.GUID, state etw.ProviderState, level etw.Level, matchAnyKeyword uint64, matchAllKeyword uint64, filterData uintptr) {
// Provider ID: {2acb92c0-eb9b-571a-69cf-8f3410f383ad} if state == etw.ProviderStateCaptureState {
// Hook isn't closed explicitly, as it will exist until process exit. dumpStacks(false)
// GUID is generated based on name - see Microsoft/go-winio/tools/etw-provider-gen. }
if hook, err := etwlogrus.NewHook("ContainerD"); err == nil { }
logrus.AddHook(hook)
func init() {
// 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) { func connectEvents(address string) (eventsapi.EventsClient, error) {
conn, err := connect(address, dialer.Dialer) conn, err := connect(address, dialer.ContextDialer)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to dial %q", address) return nil, errors.Wrapf(err, "failed to dial %q", address)
} }
return eventsapi.NewEventsClient(conn), nil 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{ gopts := []grpc.DialOption{
grpc.WithBlock(), grpc.WithBlock(),
grpc.WithInsecure(), grpc.WithInsecure(),
grpc.WithDialer(d), grpc.WithContextDialer(d),
grpc.FailOnNonTempDialError(true), grpc.FailOnNonTempDialError(true),
grpc.WithBackoffMaxDelay(3 * time.Second), grpc.WithBackoffMaxDelay(3 * time.Second),
} }

View File

@ -270,8 +270,8 @@ func registerService() error {
Delay uint32 Delay uint32
} }
t := []scAction{ t := []scAction{
{Type: scActionRestart, Delay: uint32(60 * time.Second / time.Millisecond)}, {Type: scActionRestart, Delay: uint32(15 * time.Second / time.Millisecond)},
{Type: scActionRestart, Delay: uint32(60 * time.Second / time.Millisecond)}, {Type: scActionRestart, Delay: uint32(15 * time.Second / time.Millisecond)},
{Type: scActionNone}, {Type: scActionNone},
} }
lpInfo := serviceFailureActions{ResetPeriod: uint32(24 * time.Hour / time.Second), ActionsCount: uint32(3), Actions: uintptr(unsafe.Pointer(&t[0]))} 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"
"github.com/containerd/containerd/cmd/ctr/commands/run" "github.com/containerd/containerd/cmd/ctr/commands/run"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
"github.com/containerd/typeurl" "github.com/containerd/typeurl"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -125,7 +124,7 @@ var listCommand = cli.Command{
w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t") fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t")
for _, c := range containers { for _, c := range containers {
info, err := c.Info(ctx) info, err := c.Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil { if err != nil {
return err return err
} }
@ -262,7 +261,7 @@ var infoCommand = cli.Command{
if err != nil { if err != nil {
return err return err
} }
info, err := container.Info(ctx) info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil { if err != nil {
return err return err
} }
@ -285,152 +284,3 @@ var infoCommand = cli.Command{
return nil 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" "os"
"github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/images/oci" "github.com/containerd/containerd/images/archive"
"github.com/containerd/containerd/reference" "github.com/containerd/containerd/platforms"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -31,26 +30,24 @@ import (
var exportCommand = cli.Command{ var exportCommand = cli.Command{
Name: "export", Name: "export",
Usage: "export an image", Usage: "export images",
ArgsUsage: "[flags] <out> <image>", ArgsUsage: "[flags] <out> <image> ...",
Description: `Export an image to a tar stream. Description: `Export images to an OCI tar archive.
Currently, only OCI format is supported.
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{ Flags: []cli.Flag{
// TODO(AkihiroSuda): make this map[string]string as in moby/moby#33355? cli.BoolFlag{
cli.StringFlag{ Name: "skip-manifest-json",
Name: "oci-ref-name", Usage: "do not add Docker compatible manifest.json to archive",
Value: "",
Usage: "override org.opencontainers.image.ref.name annotation",
}, },
cli.StringFlag{ cli.StringSliceFlag{
Name: "manifest", Name: "platform",
Usage: "digest of manifest", Usage: "Pull content from a specific platform",
}, Value: &cli.StringSlice{},
cli.StringFlag{
Name: "manifest-type",
Usage: "media type of manifest digest",
Value: ocispec.MediaTypeImageManifest,
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "all-platforms", Name: "all-platforms",
@ -59,43 +56,47 @@ Currently, only OCI format is supported.
}, },
Action: func(context *cli.Context) error { Action: func(context *cli.Context) error {
var ( var (
out = context.Args().First() out = context.Args().First()
local = context.Args().Get(1) images = context.Args().Tail()
desc ocispec.Descriptor 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") 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) client, ctx, cancel, err := commands.NewClient(context)
if err != nil { if err != nil {
return err return err
} }
defer cancel() defer cancel()
if manifest := context.String("manifest"); manifest != "" {
desc.Digest, err = digest.Parse(manifest) is := client.ImageService()
if err != nil { for _, img := range images {
return errors.Wrap(err, "invalid manifest digest") exportOpts = append(exportOpts, archive.WithImage(is, img))
}
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
} }
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 var w io.WriteCloser
if out == "-" { if out == "-" {
w = os.Stdout w = os.Stdout
@ -105,32 +106,8 @@ Currently, only OCI format is supported.
return nil return nil
} }
} }
defer w.Close()
var ( return client.Export(ctx, w, exportOpts...)
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()
}, },
} }
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", Name: "all-platforms",
Usage: "imports content for all platforms, false by default", Usage: "imports content for all platforms, false by default",
}, },
cli.BoolFlag{
Name: "no-unpack",
Usage: "skip unpacking the images, false by default",
},
}, commands.SnapshotterFlags...), }, commands.SnapshotterFlags...),
Action: func(context *cli.Context) error { Action: func(context *cli.Context) error {
@ -119,19 +123,21 @@ If foobar.tar contains an OCI ref named "latest" and anonymous ref "sha256:deadb
return closeErr return closeErr
} }
log.G(ctx).Debugf("unpacking %d images", len(imgs)) if !context.Bool("no-unpack") {
log.G(ctx).Debugf("unpacking %d images", len(imgs))
for _, img := range imgs { for _, img := range imgs {
// TODO: Allow configuration of the platform // TODO: Allow configuration of the platform
image := containerd.NewImage(client, img) image := containerd.NewImage(client, img)
// TODO: Show unpack status // TODO: Show unpack status
fmt.Printf("unpacking %s (%s)...", img.Name, img.Target.Digest) fmt.Printf("unpacking %s (%s)...", img.Name, img.Target.Digest)
err = image.Unpack(ctx, context.String("snapshotter")) err = image.Unpack(ctx, context.String("snapshotter"))
if err != nil { if err != nil {
return err return err
}
fmt.Println("done")
} }
fmt.Println("done")
} }
return nil return nil
}, },

View File

@ -115,6 +115,10 @@ var Command = cli.Command{
Name: "cgroup", Name: "cgroup",
Usage: "cgroup path (To disable use of cgroup, set to \"\" explicitly)", 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...)...)...), }, append(platformRunFlags, append(commands.SnapshotterFlags, commands.ContainerFlags...)...)...),
Action: func(context *cli.Context) error { Action: func(context *cli.Context) error {
var ( var (

View File

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

View File

@ -23,9 +23,12 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
"path/filepath"
"github.com/containerd/console" "github.com/containerd/console"
"github.com/containerd/containerd/cmd/ctr/commands" "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/containerd/runtime/v2/task"
"github.com/containerd/ttrpc" "github.com/containerd/ttrpc"
"github.com/containerd/typeurl" "github.com/containerd/typeurl"
@ -61,8 +64,8 @@ var Command = cli.Command{
Usage: "interact with a shim directly", Usage: "interact with a shim directly",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "socket", Name: "id",
Usage: "socket on which to connect to the shim", Usage: "container id",
}, },
}, },
Subcommands: []cli.Command{ Subcommands: []cli.Command{
@ -116,7 +119,7 @@ var stateCommand = cli.Command{
return err return err
} }
r, err := service.State(gocontext.Background(), &task.StateRequest{ r, err := service.State(gocontext.Background(), &task.StateRequest{
ID: context.Args().First(), ID: context.GlobalString("id"),
}) })
if err != nil { if err != nil {
return err return err
@ -226,20 +229,30 @@ var execCommand = cli.Command{
} }
func getTaskService(context *cli.Context) (task.TaskService, error) { func getTaskService(context *cli.Context) (task.TaskService, error) {
bindSocket := context.GlobalString("socket") id := context.GlobalString("id")
if bindSocket == "" { if id == "" {
return nil, errors.New("socket path must be specified") return nil, fmt.Errorf("container id must be specified")
}
ns := context.GlobalString("namespace")
// /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
// before, so may not be a huge deal.
return task.NewTaskClient(client), nil
}
} }
conn, err := net.Dial("unix", "\x00"+bindSocket) return nil, fmt.Errorf("fail to connect to container %s's shim", id)
if err != nil {
return nil, err
}
client := ttrpc.NewClient(conn)
// TODO(stevvooe): This actually leaks the connection. We were leaking it
// before, so may not be a huge deal.
return task.NewTaskClient(client), nil
} }

View File

@ -49,7 +49,7 @@ type Container interface {
// ID identifies the container // ID identifies the container
ID() string ID() string
// Info returns the underlying container record type // 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 removes the container
Delete(context.Context, ...DeleteOpts) error Delete(context.Context, ...DeleteOpts) error
// NewTask creates a new task based on the container metadata // NewTask creates a new task based on the container metadata
@ -80,16 +80,18 @@ type Container interface {
func containerFromRecord(client *Client, c containers.Container) *container { func containerFromRecord(client *Client, c containers.Container) *container {
return &container{ return &container{
client: client, client: client,
id: c.ID, id: c.ID,
metadata: c,
} }
} }
var _ = (Container)(&container{}) var _ = (Container)(&container{})
type container struct { type container struct {
client *Client client *Client
id string id string
metadata containers.Container
} }
// ID returns the container's unique id // ID returns the container's unique id
@ -97,8 +99,22 @@ func (c *container) ID() string {
return c.id return c.id
} }
func (c *container) Info(ctx context.Context) (containers.Container, error) { func (c *container) Info(ctx context.Context, opts ...InfoOpts) (containers.Container, error) {
return c.get(ctx) 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) { 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 // UpdateContainerOpts allows the caller to set additional options when updating a container
type UpdateContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error 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 // WithRuntime allows a user to specify the runtime name and additional options that should
// be used to create tasks for the container // be used to create tasks for the container
func WithRuntime(name string, options interface{}) NewContainerOpts { func WithRuntime(name string, options interface{}) NewContainerOpts {
@ -123,7 +132,7 @@ func WithSnapshot(id string) NewContainerOpts {
// root filesystem in read-write mode // root filesystem in read-write mode
func WithNewSnapshot(id string, i Image, opts ...snapshots.Opt) NewContainerOpts { func WithNewSnapshot(id string, i Image, opts ...snapshots.Opt) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error { 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 { if err != nil {
return err return err
} }
@ -235,3 +244,8 @@ func WithSpec(s *oci.Spec, opts ...oci.SpecOpts) NewContainerOpts {
return err 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()) 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 return p, err
} }

View File

@ -35,7 +35,6 @@ import (
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/containerd/continuity"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -661,6 +660,19 @@ func writeTimestampFile(p string, t time.Time) error {
if err != nil { if err != nil {
return err return err
} }
return atomicWrite(p, b, 0666)
return continuity.AtomicWriteFile(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 { 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 var base content.Info
for _, opt := range opts { for _, opt := range opts {
if err := opt(&base); err != nil { 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 fp := w.fp
w.fp = nil w.fp = nil

View File

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

View File

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

View File

@ -45,9 +45,15 @@ type diffRemote struct {
client diffapi.DiffClient 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{ req := &diffapi.ApplyRequest{
Diff: fromDescriptor(diff), Diff: fromDescriptor(desc),
Mounts: fromMounts(mounts), Mounts: fromMounts(mounts),
} }
resp, err := r.client.Apply(ctx, req) 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 // Apply applies the content associated with the provided digests onto the
// provided mounts. Archive content will be extracted and decompressed if // provided mounts. Archive content will be extracted and decompressed if
// necessary. // 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() t1 := time.Now()
defer func() { defer func() {
if err == nil { 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) 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 // Applier allows applying diffs between mounts
type Applier interface { type Applier interface {
// Apply applies the content referred to by the given descriptor to // 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 // implementation and content descriptor. For example, in the common
// case the descriptor is a file system difference in tar format, // case the descriptor is a file system difference in tar format,
// that tar would be applied on top of the mounts. // 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 // 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 !newReference {
if err := cw.Truncate(0); err != nil { if err = cw.Truncate(0); err != nil {
return err return err
} }
} }
if isCompressed { if isCompressed {
dgstr := digest.SHA256.Digester() 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 { if err != nil {
return errors.Wrap(err, "failed to get compressed stream") return errors.Wrap(err, "failed to get compressed stream")
} }

View File

@ -26,7 +26,11 @@
// client-side errors to the correct types. // client-side errors to the correct types.
package errdefs package errdefs
import "github.com/pkg/errors" import (
"context"
"github.com/pkg/errors"
)
// Definitions of common error types used throughout containerd. All containerd // Definitions of common error types used throughout containerd. All containerd
// errors returned by most packages will map into one of these errors classes. // 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 { func IsNotImplemented(err error) bool {
return errors.Cause(err) == ErrNotImplemented 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 package errdefs
import ( import (
"context"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -55,6 +56,10 @@ func ToGRPC(err error) error {
return status.Errorf(codes.Unavailable, err.Error()) return status.Errorf(codes.Unavailable, err.Error())
case IsNotImplemented(err): case IsNotImplemented(err):
return status.Errorf(codes.Unimplemented, err.Error()) 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 return err
@ -89,6 +94,10 @@ func FromGRPC(err error) error {
cls = ErrFailedPrecondition cls = ErrFailedPrecondition
case codes.Unimplemented: case codes.Unimplemented:
cls = ErrNotImplemented cls = ErrNotImplemented
case codes.Canceled:
cls = context.Canceled
case codes.DeadlineExceeded:
cls = context.DeadlineExceeded
default: default:
cls = ErrUnknown cls = ErrUnknown
} }

View File

@ -20,26 +20,12 @@ import (
"context" "context"
"io" "io"
"github.com/containerd/containerd/images/oci" "github.com/containerd/containerd/images/archive"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
) )
// Export exports an image to a Tar stream. // Export exports images to a Tar stream.
// OCI format is used by default. // The tar archive is in OCI format with a Docker compatible manifest
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc. // when a single target platform is given.
// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream. func (c *Client) Export(ctx context.Context, w io.Writer, opts ...archive.ExportOpt) error {
func (c *Client) Export(ctx context.Context, desc ocispec.Descriptor, opts ...oci.V1ExporterOpt) (io.ReadCloser, error) { return archive.Export(ctx, c.ContentStore(), w, opts...)
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
} }

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{ mfstdesc.Annotations = map[string]string{
ocispec.AnnotationRefName: normalized, images.AnnotationImageName: normalized,
ocispec.AnnotationRefName: ociReferenceName(normalized),
} }
idx.Manifests = append(idx.Manifests, mfstdesc) idx.Manifests = append(idx.Manifests, mfstdesc)
@ -197,10 +198,7 @@ func onUntarJSON(r io.Reader, j interface{}) error {
if err != nil { if err != nil {
return err return err
} }
if err := json.Unmarshal(b, j); err != nil { return json.Unmarshal(b, j)
return err
}
return nil
} }
func onUntarBlob(ctx context.Context, r io.Reader, store content.Ingester, size int64, ref string) (digest.Digest, error) { 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 ( import (
"strings" "strings"
"github.com/docker/distribution/reference" "github.com/containerd/containerd/reference"
distref "github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -69,7 +70,7 @@ func isImagePrefix(s, prefix string) bool {
func normalizeReference(ref string) (string, error) { func normalizeReference(ref string) (string, error) {
// TODO: Replace this function to not depend on reference package // TODO: Replace this function to not depend on reference package
normalized, err := reference.ParseDockerRef(ref) normalized, err := distref.ParseDockerRef(ref)
if err != nil { if err != nil {
return "", errors.Wrapf(err, "normalize image ref %q", ref) return "", errors.Wrapf(err, "normalize image ref %q", ref)
} }
@ -77,6 +78,31 @@ func normalizeReference(ref string) (string, error) {
return normalized.String(), nil 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 // DigestTranslator creates a digest reference by adding the
// digest to an image name // digest to an image name
func DigestTranslator(prefix string) func(digest.Digest) string { 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,16 +130,12 @@ func (c *Client) Import(ctx context.Context, reader io.Reader, opts ...ImportOpt
} }
for _, m := range idx.Manifests { for _, m := range idx.Manifests {
if ref := m.Annotations[ocispec.AnnotationRefName]; ref != "" { name := imageName(m.Annotations, iopts.imageRefT)
if iopts.imageRefT != nil { if name != "" {
ref = iopts.imageRefT(ref) imgs = append(imgs, images.Image{
} Name: name,
if ref != "" { Target: m,
imgs = append(imgs, images.Image{ })
Name: ref,
Target: m,
})
}
} }
if iopts.dgstRefT != nil { if iopts.dgstRefT != nil {
ref := iopts.dgstRefT(m.Digest) ref := iopts.dgstRefT(m.Digest)
@ -178,3 +174,17 @@ func (c *Client) Import(ctx context.Context, reader io.Reader, opts ...ImportOpt
return imgs, nil 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) Create(context.Context, ...Opt) (Lease, error)
Delete(context.Context, Lease, ...DeleteOpt) error Delete(context.Context, Lease, ...DeleteOpt) error
List(context.Context, ...string) ([]Lease, 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 // Lease retains resources to prevent cleanup before
@ -42,6 +45,13 @@ type Lease struct {
Labels map[string]string 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 // DeleteOptions provide options on image delete
type DeleteOptions struct { type DeleteOptions struct {
Synchronous bool Synchronous bool

View File

@ -91,3 +91,43 @@ func (pm *proxyManager) List(ctx context.Context, filters ...string) ([]leases.L
return l, nil 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 { 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() nw.l.RLock()
defer nw.l.RUnlock() defer nw.l.RUnlock()
@ -767,11 +769,7 @@ func writeExpireAt(expire time.Time, bkt *bolt.Bucket) error {
if err != nil { if err != nil {
return err return err
} }
if err := bkt.Put(bucketKeyExpireAt, expireAt); err != nil { return bkt.Put(bucketKeyExpireAt, expireAt)
return err
}
return nil
} }
func (cs *contentStore) garbageCollect(ctx context.Context) (d time.Duration, err error) { func (cs *contentStore) garbageCollect(ctx context.Context) (d time.Duration, err error) {

View File

@ -18,6 +18,8 @@ package metadata
import ( import (
"context" "context"
"fmt"
"strings"
"time" "time"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
@ -167,6 +169,128 @@ func (lm *LeaseManager) List(ctx context.Context, fs ...string) ([]leases.Lease,
return ll, nil 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 { func addSnapshotLease(ctx context.Context, tx *bolt.Tx, snapshotter, key string) error {
lid, ok := leases.FromContext(ctx) lid, ok := leases.FromContext(ctx)
if !ok { if !ok {
@ -307,3 +431,36 @@ func removeIngestLease(ctx context.Context, tx *bolt.Tx, ref string) error {
return bkt.Delete([]byte(ref)) 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 // UnmountAll repeatedly unmounts the given mount point until there
// are no mounts remaining (EINVAL is returned by mount), which is // are no mounts remaining (EINVAL is returned by mount), which is
// useful for undoing a stack of mounts on the same mount point. // 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 { func UnmountAll(mount string, flags int) error {
if mount == "" {
return nil
}
if _, err := os.Stat(mount); os.IsNotExist(err) {
return nil
}
for { for {
if err := unmount(mount, flags); err != nil { if err := unmount(mount, flags); err != nil {
// EINVAL is returned if the target is not a // 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 // WithNamespace sets a given namespace on the context
func WithNamespace(ctx context.Context, namespace string) context.Context { func WithNamespace(ctx context.Context, namespace string) context.Context {
ctx = context.WithValue(ctx, namespaceKey{}, namespace) // set our key for namespace ctx = context.WithValue(ctx, namespaceKey{}, namespace) // set our key for namespace
// also store on the grpc and ttrpc headers so it gets picked up by any clients that
// also store on the grpc headers so it gets picked up by any clients that
// are using this. // are using this.
return withGRPCNamespaceHeader(ctx, namespace) return withTTRPCNamespaceHeader(withGRPCNamespaceHeader(ctx, namespace), namespace)
} }
// NamespaceFromEnv uses the namespace defined in CONTAINERD_NAMESPACE or // 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) { func Namespace(ctx context.Context) (string, bool) {
namespace, ok := ctx.Value(namespaceKey{}).(string) namespace, ok := ctx.Value(namespaceKey{}).(string)
if !ok { if !ok {
return fromGRPCHeader(ctx) if namespace, ok = fromGRPCHeader(ctx); !ok {
return fromTTRPCHeader(ctx)
}
} }
return namespace, ok return namespace, ok
} }
@ -70,10 +70,8 @@ func NamespaceRequired(ctx context.Context) (string, error) {
if !ok || namespace == "" { if !ok || namespace == "" {
return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "namespace is required") return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "namespace is required")
} }
if err := Validate(namespace); err != nil { if err := Validate(namespace); err != nil {
return "", errors.Wrap(err, "namespace validation") return "", errors.Wrap(err, "namespace validation")
} }
return namespace, nil 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 package dialer
import ( import (
"context"
"net" "net"
"time" "time"
@ -28,8 +29,19 @@ type dialResult struct {
err error 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 // 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 ( var (
stopC = make(chan struct{}) stopC = make(chan struct{})
synC = make(chan *dialResult) 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 // Only returns a match comparer for a single platform
// using default resolution logic for the 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 ARMv7, will also match ARMv6 and ARMv5
// For ARMv6, will also match ARMv5 // For ARMv6, will also match ARMv5
func Only(platform specs.Platform) MatchComparer { func Only(platform specs.Platform) MatchComparer {
platform = Normalize(platform) platform = Normalize(platform)
if platform.Architecture == "arm" { 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" { if platform.Variant == "v7" {
return orderedPlatformComparer{ return orderedPlatformComparer{
matchers: []Matcher{ matchers: []Matcher{

View File

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

View File

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

View File

@ -88,7 +88,7 @@ func appendDistributionSourceLabel(originLabel, repo string) string {
} }
repos = append(repos, repo) 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++ { for i := 1; i < len(repos); i++ {
tmp, j := repos[i], i-1 tmp, j := repos[i], i-1
for ; j >= 0 && repos[j] >= tmp; j-- { for ; j >= 0 && repos[j] >= tmp; j-- {

View File

@ -18,10 +18,10 @@ package docker
import ( import (
"context" "context"
"io"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"strconv"
"strings" "strings"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
@ -29,6 +29,7 @@ import (
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
"github.com/containerd/containerd/reference" "github.com/containerd/containerd/reference"
"github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/remotes/docker/schema1"
"github.com/containerd/containerd/version" "github.com/containerd/containerd/version"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" 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{} var _ remotes.Resolver = &dockerResolver{}
func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocispec.Descriptor, error) { 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) 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 // 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 // content headers to assemble a descriptor for the name. when this becomes
// more robust, we mostly get this information from a secure trust store. // more robust, we mostly get this information from a secure trust store.
dgstHeader := digest.Digest(resp.Header.Get("Docker-Content-Digest")) dgstHeader := digest.Digest(resp.Header.Get("Docker-Content-Digest"))
contentType := getManifestMediaType(resp)
if dgstHeader != "" { if dgstHeader != "" && size != -1 {
if err := dgstHeader.Validate(); err != nil { if err := dgstHeader.Validate(); err != nil {
return "", ocispec.Descriptor{}, errors.Wrapf(err, "%q in header not a valid digest", dgstHeader) return "", ocispec.Descriptor{}, errors.Wrapf(err, "%q in header not a valid digest", dgstHeader)
} }
dgst = dgstHeader dgst = dgstHeader
} } else {
log.G(ctx).Debug("no Docker-Content-Digest header, fetching manifest instead")
if dgst == "" { req, err := http.NewRequest(http.MethodGet, u, nil)
return "", ocispec.Descriptor{}, errors.Errorf("could not resolve digest for %v", ref) if err != nil {
} return "", ocispec.Descriptor{}, err
}
req.Header = r.headers
var ( resp, err := fetcher.doRequestWithRetries(ctx, req, nil)
size int64 if err != nil {
sizeHeader = resp.Header.Get("Content-Length") return "", ocispec.Descriptor{}, err
) }
defer resp.Body.Close()
size, err = strconv.ParseInt(sizeHeader, 10, 64) bodyReader := countingReader{reader: resp.Body}
if err != nil {
return "", ocispec.Descriptor{}, errors.Wrapf(err, "invalid size header: %q", sizeHeader) contentType = getManifestMediaType(resp)
} if contentType == images.MediaTypeDockerSchema1Manifest {
if size < 0 { b, err := schema1.ReadStripSignature(&bodyReader)
return "", ocispec.Descriptor{}, errors.Errorf("%q in header not a valid size", sizeHeader) 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{ desc := ocispec.Descriptor{
Digest: dgst, Digest: dgst,
MediaType: resp.Header.Get("Content-Type"), // need to strip disposition? MediaType: contentType,
Size: size, Size: size,
} }

View File

@ -227,6 +227,17 @@ func (c *Converter) Convert(ctx context.Context, opts ...ConvertOpt) (ocispec.De
return desc, nil 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 { func (c *Converter) fetchManifest(ctx context.Context, desc ocispec.Descriptor) error {
log.G(ctx).Debug("fetch schema 1") log.G(ctx).Debug("fetch schema 1")
@ -235,17 +246,12 @@ func (c *Converter) fetchManifest(ctx context.Context, desc ocispec.Descriptor)
return err return err
} }
b, err := ioutil.ReadAll(io.LimitReader(rc, manifestSizeLimit)) // limit to 8MB b, err := ReadStripSignature(rc)
rc.Close() rc.Close()
if err != nil { if err != nil {
return err return err
} }
b, err = stripSignature(b)
if err != nil {
return err
}
var m manifest var m manifest
if err := json.Unmarshal(b, &m); err != nil { if err := json.Unmarshal(b, &m); err != nil {
return err 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 // PusherFunc allows package users to implement a Pusher with just a
// function. // 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 // Push content
func (fn PusherFunc) Push(ctx context.Context, desc ocispec.Descriptor, r io.Reader) error { func (fn PusherFunc) Push(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error) {
return fn(ctx, desc, r) 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) 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 return nil, err
} }
err = ioutil.WriteFile(filepath.Join(path, configFilename), spec, 0666) 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 // create a hidden dir for an atomic removal
atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path))) atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
if err := os.Rename(path, atomicPath); err != nil { if err := os.Rename(path, atomicPath); err != nil {
if os.IsNotExist(err) {
return nil
}
return err return err
} }
return os.RemoveAll(atomicPath) return os.RemoveAll(atomicPath)

View File

@ -22,6 +22,7 @@ import (
"context" "context"
"github.com/containerd/console" "github.com/containerd/console"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/runtime/proc" "github.com/containerd/containerd/runtime/proc"
google_protobuf "github.com/gogo/protobuf/types" google_protobuf "github.com/gogo/protobuf/types"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -55,11 +56,11 @@ func (s *deletedState) Start(ctx context.Context) error {
} }
func (s *deletedState) Delete(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 { 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) { 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) 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 // Pause the init process and all its child processes
func (p *Init) Pause(ctx context.Context) error { func (p *Init) Pause(ctx context.Context) error {
p.mu.Lock() p.mu.Lock()

View File

@ -21,7 +21,6 @@ package proc
import ( import (
"context" "context"
"github.com/containerd/console"
"github.com/containerd/containerd/runtime/proc" "github.com/containerd/containerd/runtime/proc"
runc "github.com/containerd/go-runc" runc "github.com/containerd/go-runc"
google_protobuf "github.com/gogo/protobuf/types" google_protobuf "github.com/gogo/protobuf/types"
@ -30,7 +29,6 @@ import (
) )
type initState interface { type initState interface {
Resize(console.WinSize) error
Start(context.Context) error Start(context.Context) error
Delete(context.Context) error Delete(context.Context) error
Pause(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") 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 { func (s *createdState) Start(ctx context.Context) error {
if err := s.p.start(ctx); err != nil { if err := s.p.start(ctx); err != nil {
return err 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") 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 { func (s *createdCheckpointState) Start(ctx context.Context) error {
p := s.p p := s.p
sio := p.stdio sio := p.stdio
@ -255,10 +245,6 @@ func (s *runningState) Checkpoint(ctx context.Context, r *CheckpointConfig) erro
return s.p.checkpoint(ctx, r) 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 { func (s *runningState) Start(ctx context.Context) error {
return errors.Errorf("cannot start a running process") 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) 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 { func (s *pausedState) Start(ctx context.Context) error {
return errors.Errorf("cannot start a paused process") 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") 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 { func (s *stoppedState) Start(ctx context.Context) error {
return errors.Errorf("cannot start a stopped process") 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() out, err := newPipe()
if err != nil { if err != nil {
cancel()
return nil, err return nil, err
} }
serr, err := newPipe() serr, err := newPipe()
if err != nil { if err != nil {
cancel()
return nil, err return nil, err
} }
r, w, err := os.Pipe() r, w, err := os.Pipe()
if err != nil { if err != nil {
cancel()
return nil, err return nil, err
} }
cmd.ExtraFiles = append(cmd.ExtraFiles, out.r, serr.r, w) cmd.ExtraFiles = append(cmd.ExtraFiles, out.r, serr.r, w)
// don't need to register this with the reaper or wait when // don't need to register this with the reaper or wait when
// running inside a shim // running inside a shim
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
cancel()
return nil, err return nil, err
} }
// close our side of the pipe after start // 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 // wait for the logging binary to be ready
b := make([]byte, 1) b := make([]byte, 1)
if _, err := r.Read(b); err != nil && err != io.EOF { if _, err := r.Read(b); err != nil && err != io.EOF {
cancel()
return nil, err return nil, err
} }
return &binaryIO{ return &binaryIO{

View File

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

View File

@ -191,18 +191,13 @@ func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts
} }
exitHandler := func() { exitHandler := func() {
log.G(ctx).WithField("id", id).Info("shim reaped") 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 // Task was never started or was already successfully deleted
return return
} }
lc := t.(*Task)
log.G(ctx).WithFields(logrus.Fields{ if err = r.cleanupAfterDeadShim(context.Background(), bundle, namespace, id); err != nil {
"id": id,
"namespace": namespace,
}).Warn("cleaning up after killed shim")
if err = r.cleanupAfterDeadShim(context.Background(), bundle, namespace, id, lc.pid); err != nil {
log.G(ctx).WithError(err).WithFields(logrus.Fields{ log.G(ctx).WithError(err).WithFields(logrus.Fields{
"id": id, "id": id,
"namespace": namespace, "namespace": namespace,
@ -330,6 +325,10 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
continue continue
} }
id := path.Name() id := path.Name()
// skip hidden directories
if len(id) > 0 && id[0] == '.' {
continue
}
bundle := loadBundle( bundle := loadBundle(
id, id,
filepath.Join(r.state, ns, 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) ctx = namespaces.WithNamespace(ctx, ns)
pid, _ := runc.ReadPidFile(filepath.Join(bundle.path, proc.InitPidFile)) pid, _ := runc.ReadPidFile(filepath.Join(bundle.path, proc.InitPidFile))
shimExit := make(chan struct{})
s, err := bundle.NewShimClient(ctx, ns, ShimConnect(r.config, func() { s, err := bundle.NewShimClient(ctx, ns, ShimConnect(r.config, func() {
err := r.cleanupAfterDeadShim(ctx, bundle, ns, id, pid) defer close(shimExit)
if err != nil { 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). log.G(ctx).WithError(err).WithField("bundle", bundle.path).
Error("cleaning up after dead shim") Error("cleaning up after dead shim")
} }
@ -349,7 +354,7 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
"id": id, "id": id,
"namespace": ns, "namespace": ns,
}).Error("connecting to shim") }).Error("connecting to shim")
err := r.cleanupAfterDeadShim(ctx, bundle, ns, id, pid) err := r.cleanupAfterDeadShim(ctx, bundle, ns, id)
if err != nil { if err != nil {
log.G(ctx).WithError(err).WithField("bundle", bundle.path). log.G(ctx).WithError(err).WithField("bundle", bundle.path).
Error("cleaning up after dead shim") 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) 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) shimStdoutLog, err := v1.OpenShimStdoutLog(ctx, logDirPath)
if err != nil { if err != nil {
log.G(ctx).WithError(err).WithFields(logrus.Fields{ 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") }).Error("opening shim stdout log pipe")
continue 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) shimStderrLog, err := v1.OpenShimStderrLog(ctx, logDirPath)
if err != nil { if err != nil {
@ -379,7 +400,11 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
}).Error("opening shim stderr log pipe") }).Error("opening shim stderr log pipe")
continue 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) t, err := newTask(id, ns, pid, s, r.events, r.tasks, bundle)
if err != nil { if err != nil {
@ -391,7 +416,13 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
return o, nil 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) ctx = namespaces.WithNamespace(ctx, ns)
if err := r.terminate(ctx, bundle, ns, id); err != nil { if err := r.terminate(ctx, bundle, ns, id); err != nil {
if r.config.ShimDebug { if r.config.ShimDebug {
@ -414,6 +445,10 @@ func (r *Runtime) cleanupAfterDeadShim(ctx context.Context, bundle *bundle, ns,
if err := bundle.Delete(); err != nil { if err := bundle.Delete(); err != nil {
log.G(ctx).WithError(err).Error("delete bundle") 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{ r.events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{
ContainerID: id, ContainerID: id,

View File

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

View File

@ -26,6 +26,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"sync" "sync"
"syscall" "syscall"
@ -98,9 +99,9 @@ func WithStart(binary, address, daemonAddress, cgroup string, debug bool, exitHa
cmd.Wait() cmd.Wait()
exitHandler() exitHandler()
if stdoutLog != nil { if stdoutLog != nil {
stderrLog.Close() stdoutLog.Close()
} }
if stdoutLog != nil { if stderrLog != nil {
stderrLog.Close() stderrLog.Close()
} }
}() }()
@ -110,7 +111,10 @@ func WithStart(binary, address, daemonAddress, cgroup string, debug bool, exitHa
"debug": debug, "debug": debug,
}).Infof("shim %s started", binary) }).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 return nil, nil, err
} }
// set shim in cgroup if it is provided // 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 return cmd, nil
} }
// writeAddress writes a address file atomically // writeFile writes a address file atomically
func writeAddress(path, address string) error { func writeFile(path, address string) error {
path, err := filepath.Abs(path) path, err := filepath.Abs(path)
if err != nil { if err != nil {
return err return err
@ -219,8 +223,7 @@ func WithConnect(address string, onClose func()) Opt {
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
client := ttrpc.NewClient(conn) client := ttrpc.NewClient(conn, ttrpc.WithOnClose(onClose))
client.OnClose(onClose)
return shimapi.NewShimClient(client), conn, nil 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{ config := &proc.CreateConfig{
ID: r.ID, ID: r.ID,
Bundle: r.Bundle, Bundle: r.Bundle,
@ -137,7 +145,6 @@ func (s *Service) Create(ctx context.Context, r *shimapi.CreateTaskRequest) (_ *
ParentCheckpoint: r.ParentCheckpoint, ParentCheckpoint: r.ParentCheckpoint,
Options: r.Options, Options: r.Options,
} }
rootfs := filepath.Join(r.Bundle, "rootfs")
defer func() { defer func() {
if err != nil { if err != nil {
if err2 := mount.UnmountAll(rootfs, 0); err2 != 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.config.SystemdCgroup,
s.platform, s.platform,
config, config,
rootfs,
) )
if err != nil { if err != nil {
return nil, errdefs.ToGRPC(err) return nil, errdefs.ToGRPC(err)
@ -546,7 +554,7 @@ func shouldKillAllOnExit(bundlePath string) (bool, error) {
if bundleSpec.Linux != nil { if bundleSpec.Linux != nil {
for _, ns := range bundleSpec.Linux.Namespaces { for _, ns := range bundleSpec.Linux.Namespaces {
if ns.Type == specs.PIDNamespace { if ns.Type == specs.PIDNamespace && ns.Path == "" {
return false, nil return false, nil
} }
} }
@ -632,7 +640,7 @@ func getTopic(ctx context.Context, e interface{}) string {
return runtime.TaskUnknownTopic 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 var options runctypes.CreateOptions
if r.Options != nil { if r.Options != nil {
v, err := typeurl.UnmarshalAny(r.Options) 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) options = *v.(*runctypes.CreateOptions)
} }
rootfs := filepath.Join(path, "rootfs")
runtime := proc.NewRunc(runtimeRoot, path, namespace, r.Runtime, criu, systemdCgroup) runtime := proc.NewRunc(runtimeRoot, path, namespace, r.Runtime, criu, systemdCgroup)
p := proc.New(r.ID, runtime, rproc.Stdio{ p := proc.New(r.ID, runtime, rproc.Stdio{
Stdin: r.Stdin, Stdin: r.Stdin,

View File

@ -30,6 +30,7 @@ import (
client "github.com/containerd/containerd/runtime/v2/shim" client "github.com/containerd/containerd/runtime/v2/shim"
"github.com/containerd/containerd/runtime/v2/task" "github.com/containerd/containerd/runtime/v2/task"
"github.com/containerd/ttrpc" "github.com/containerd/ttrpc"
"github.com/gogo/protobuf/types"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -52,7 +53,7 @@ type binary struct {
rtTasks *runtime.TaskList 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} args := []string{"-id", b.bundle.ID}
if logrus.GetLevel() == logrus.DebugLevel { if logrus.GetLevel() == logrus.DebugLevel {
args = append(args, "-debug") args = append(args, "-debug")
@ -64,6 +65,7 @@ func (b *binary) Start(ctx context.Context) (_ *shim, err error) {
b.runtime, b.runtime,
b.containerdAddress, b.containerdAddress,
b.bundle.Path, b.bundle.Path,
opts,
args..., args...,
) )
if err != nil { if err != nil {
@ -84,7 +86,12 @@ func (b *binary) Start(ctx context.Context) (_ *shim, err error) {
go func() { go func() {
defer f.Close() defer f.Close()
if _, err := io.Copy(os.Stderr, f); err != nil { if _, err := io.Copy(os.Stderr, f); err != nil {
log.G(ctx).WithError(err).Error("copy shim log") // 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() out, err := cmd.CombinedOutput()
@ -96,8 +103,7 @@ func (b *binary) Start(ctx context.Context) (_ *shim, err error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
client := ttrpc.NewClient(conn) client := ttrpc.NewClient(conn, ttrpc.WithOnClose(onClose))
client.OnClose(func() { conn.Close() })
return &shim{ return &shim{
bundle: b.bundle, bundle: b.bundle,
client: client, client: client,
@ -122,6 +128,7 @@ func (b *binary) Delete(ctx context.Context) (*runtime.Exit, error) {
b.runtime, b.runtime,
b.containerdAddress, b.containerdAddress,
bundlePath, bundlePath,
nil,
"-id", b.bundle.ID, "-id", b.bundle.ID,
"-bundle", b.bundle.Path, "-bundle", b.bundle.Path,
"delete") "delete")
@ -148,9 +155,6 @@ func (b *binary) Delete(ctx context.Context) (*runtime.Exit, error) {
if err := b.bundle.Delete(); err != nil { if err := b.bundle.Delete(); err != nil {
return nil, err 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{ return &runtime.Exit{
Status: response.ExitStatus, Status: response.ExitStatus,
Timestamp: response.ExitedAt, Timestamp: response.ExitedAt,

View File

@ -24,6 +24,7 @@ import (
"path/filepath" "path/filepath"
"github.com/containerd/containerd/identifiers" "github.com/containerd/containerd/identifiers"
"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/pkg/errors" "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 { if err := os.MkdirAll(filepath.Dir(work), 0711); err != nil {
return nil, err 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 err := os.Mkdir(work, 0711); err != nil {
if !os.IsExist(err) { if !os.IsExist(err) {
return nil, 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) paths = append(paths, work)
// create rootfs dir
if err := os.Mkdir(filepath.Join(b.Path, "rootfs"), 0711); err != nil {
return nil, err
}
// symlink workdir // symlink workdir
if err := os.Symlink(work, filepath.Join(b.Path, "work")); err != nil { if err := os.Symlink(work, filepath.Join(b.Path, "work")); err != nil {
return nil, err return nil, err
@ -115,6 +117,13 @@ type Bundle struct {
// Delete a bundle atomically // Delete a bundle atomically
func (b *Bundle) Delete() error { func (b *Bundle) Delete() error {
work, werr := os.Readlink(filepath.Join(b.Path, "work")) 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) err := atomicDelete(b.Path)
if err == nil { if err == nil {
if werr == nil { if werr == nil {
@ -138,6 +147,9 @@ func atomicDelete(path string) error {
// create a hidden dir for an atomic removal // create a hidden dir for an atomic removal
atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path))) atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
if err := os.Rename(path, atomicPath); err != nil { if err := os.Rename(path, atomicPath); err != nil {
if os.IsNotExist(err) {
return nil
}
return err return err
} }
return os.RemoveAll(atomicPath) return os.RemoveAll(atomicPath)

View File

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

View File

@ -24,18 +24,21 @@ import (
"path/filepath" "path/filepath"
"time" "time"
eventstypes "github.com/containerd/containerd/api/events"
"github.com/containerd/containerd/api/types" "github.com/containerd/containerd/api/types"
tasktypes "github.com/containerd/containerd/api/types/task" tasktypes "github.com/containerd/containerd/api/types/task"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/events/exchange" "github.com/containerd/containerd/events/exchange"
"github.com/containerd/containerd/identifiers" "github.com/containerd/containerd/identifiers"
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/runtime" "github.com/containerd/containerd/runtime"
client "github.com/containerd/containerd/runtime/v2/shim" client "github.com/containerd/containerd/runtime/v2/shim"
"github.com/containerd/containerd/runtime/v2/task" "github.com/containerd/containerd/runtime/v2/task"
"github.com/containerd/ttrpc" "github.com/containerd/ttrpc"
ptypes "github.com/gogo/protobuf/types" ptypes "github.com/gogo/protobuf/types"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
) )
func loadAddress(path string) (string, error) { func loadAddress(path string) (string, error) {
@ -46,7 +49,7 @@ func loadAddress(path string) (string, error) {
return string(data), nil 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")) address, err := loadAddress(filepath.Join(bundle.Path, "address"))
if err != nil { if err != nil {
return nil, err return nil, err
@ -55,6 +58,11 @@ func loadShim(ctx context.Context, bundle *Bundle, events *exchange.Exchange, rt
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() {
if err != nil {
conn.Close()
}
}()
f, err := openShimLog(ctx, bundle) f, err := openShimLog(ctx, bundle)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "open shim log pipe") 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() { go func() {
defer f.Close() defer f.Close()
if _, err := io.Copy(os.Stderr, f); err != nil { if _, err := io.Copy(os.Stderr, f); err != nil {
log.G(ctx).WithError(err).Error("copy shim log") // 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 := ttrpc.NewClient(conn, ttrpc.WithOnClose(onClose))
client.OnClose(func() { conn.Close() }) defer func() {
if err != nil {
client.Close()
}
}()
s := &shim{ s := &shim{
client: client, client: client,
task: task.NewTaskClient(client), task: task.NewTaskClient(client),
@ -89,6 +106,52 @@ func loadShim(ctx context.Context, bundle *Bundle, events *exchange.Exchange, rt
return s, nil 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 { type shim struct {
bundle *Bundle bundle *Bundle
client *ttrpc.Client client *ttrpc.Client
@ -120,19 +183,9 @@ func (s *shim) Shutdown(ctx context.Context) error {
} }
func (s *shim) waitShutdown(ctx context.Context) error { func (s *shim) waitShutdown(ctx context.Context) error {
dead := make(chan struct{}) ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
go func() { defer cancel()
if err := s.Shutdown(ctx); err != nil { return s.Shutdown(ctx)
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
}
} }
// ID of the shim/task // 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{ response, err := s.task.Delete(ctx, &task.DeleteRequest{
ID: s.ID(), ID: s.ID(),
}) })
if err != nil { if err != nil && !errdefs.IsNotFound(err) {
return nil, errdefs.FromGRPC(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 // remove self from the runtime task list
// this seems dirty but it cleans up the API across runtimes, tasks, and the service // this seems dirty but it cleans up the API across runtimes, tasks, and the service
s.rtTasks.Delete(ctx, s.ID()) 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{ return &runtime.Exit{
Status: response.ExitStatus, Status: response.ExitStatus,
Timestamp: response.ExitedAt, 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 ( import (
"context" "context"
"net"
"sync" "sync"
"time" "time"
v1 "github.com/containerd/containerd/api/services/ttrpc/events/v1" v1 "github.com/containerd/containerd/api/services/ttrpc/events/v1"
"github.com/containerd/containerd/events" "github.com/containerd/containerd/events"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/pkg/ttrpcutil"
"github.com/containerd/ttrpc"
"github.com/containerd/typeurl" "github.com/containerd/typeurl"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -40,20 +41,24 @@ type item struct {
count int 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{ l := &remoteEventsPublisher{
dialer: newDialier(func() (net.Conn, error) { client: client,
return connect(address, dial)
}),
closed: make(chan struct{}), closed: make(chan struct{}),
requeue: make(chan *item, queueSize), requeue: make(chan *item, queueSize),
} }
go l.processQueue() go l.processQueue()
return l return l, nil
} }
type remoteEventsPublisher struct { type remoteEventsPublisher struct {
dialer *dialer client *ttrpcutil.Client
closed chan struct{} closed chan struct{}
closer sync.Once closer sync.Once
requeue chan *item requeue chan *item
@ -64,7 +69,7 @@ func (l *remoteEventsPublisher) Done() <-chan struct{} {
} }
func (l *remoteEventsPublisher) Close() (err error) { func (l *remoteEventsPublisher) Close() (err error) {
err = l.dialer.Close() err = l.client.Close()
l.closer.Do(func() { l.closer.Do(func() {
close(l.closed) close(l.closed)
}) })
@ -79,18 +84,7 @@ func (l *remoteEventsPublisher) processQueue() {
continue continue
} }
client, err := l.dialer.Get() if err := l.forwardRequest(i.ctx, &v1.ForwardRequest{Envelope: i.ev}); err != nil {
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)
logrus.WithError(err).Error("forward event") logrus.WithError(err).Error("forward event")
l.queue(i) l.queue(i)
} }
@ -124,22 +118,33 @@ func (l *remoteEventsPublisher) Publish(ctx context.Context, topic string, event
}, },
ctx: ctx, ctx: ctx,
} }
client, err := l.dialer.Get()
if err != nil { if err := l.forwardRequest(i.ctx, &v1.ForwardRequest{Envelope: i.ev}); 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)
l.queue(i) l.queue(i)
return err return err
} }
return nil return nil
} }
func connect(address string, d func(string, time.Duration) (net.Conn, error)) (net.Conn, error) { func (l *remoteEventsPublisher) forwardRequest(ctx context.Context, req *v1.ForwardRequest) error {
return d(address, 5*time.Second) _, 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 NoSubreaper bool
// NoReaper disables the shim binary from reaping any child process implicitly // NoReaper disables the shim binary from reaping any child process implicitly
NoReaper bool NoReaper bool
// NoSetupLogger disables automatic configuration of logrus to use the shim FIFO
NoSetupLogger bool
} }
var ( var (
@ -160,9 +162,13 @@ func run(id string, initFunc Init, config Config) error {
return err 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() defer publisher.Close()
if namespaceFlag == "" { if namespaceFlag == "" {
@ -206,8 +212,10 @@ func run(id string, initFunc Init, config Config) error {
} }
return nil return nil
default: default:
if err := setLogger(ctx, idFlag); err != nil { if !config.NoSetupLogger {
return err if err := setLogger(ctx, idFlag); err != nil {
return err
}
} }
client := NewShimClient(ctx, service, signals) client := NewShimClient(ctx, service, signals)
if err := client.Serve(); err != nil { if err := client.Serve(); err != nil {

View File

@ -24,9 +24,7 @@ import (
"net" "net"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"time"
"github.com/containerd/fifo" "github.com/containerd/fifo"
"github.com/pkg/errors" "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) { func openLog(ctx context.Context, _ string) (io.Writer, error) {
return fifo.OpenFifo(ctx, "log", unix.O_WRONLY, 0700) 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" "net"
"os" "os"
"sync" "sync"
"time"
"unsafe" "unsafe"
winio "github.com/Microsoft/go-winio" winio "github.com/Microsoft/go-winio"
@ -286,35 +285,3 @@ func openLog(ctx context.Context, id string) (io.Writer, error) {
go dswl.beginAccept() go dswl.beginAccept()
return dswl, nil 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 package shim
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -29,13 +30,15 @@ import (
"time" "time"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/gogo/protobuf/proto"
"github.com/gogo/protobuf/types"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
var runtimePaths sync.Map var runtimePaths sync.Map
// Command returns the shim command with the provided args and configuration // 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) ns, err := namespaces.NamespaceRequired(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@ -94,6 +97,13 @@ func Command(ctx context.Context, runtime, containerdAddress, path string, cmdAr
cmd.Dir = path cmd.Dir = path
cmd.Env = append(os.Environ(), "GOMAXPROCS=2") cmd.Env = append(os.Environ(), "GOMAXPROCS=2")
cmd.SysProcAttr = getSysProcAttr() 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 return cmd, nil
} }

View File

@ -28,5 +28,5 @@ import (
) )
func openShimLog(ctx context.Context, bundle *Bundle) (io.ReadCloser, error) { 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) mounts = toMounts(er.Mounts)
) )
var opts []diff.ApplyOpt
for _, differ := range l.differs { for _, differ := range l.differs {
ocidesc, err = differ.Apply(ctx, desc, mounts) ocidesc, err = differ.Apply(ctx, desc, mounts, opts...)
if !errdefs.IsNotImplemented(err) { if !errdefs.IsNotImplemented(err) {
break break
} }

View File

@ -107,3 +107,27 @@ func (l *local) List(ctx context.Context, filters ...string) ([]leases.Lease, er
} }
return ll, nil 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 }, 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 { func leaseToGRPC(l leases.Lease) *api.Lease {
return &api.Lease{ return &api.Lease{
ID: l.ID, ID: l.ID,

View File

@ -17,13 +17,18 @@
package config package config
import ( import (
"strings"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/plugin"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// Config provides containerd configuration data for the server // Config provides containerd configuration data for the server
type Config struct { 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 is the path to a directory where containerd will store persistent data
Root string `toml:"root"` Root string `toml:"root"`
// State is the path to a directory where containerd will store transient data // State is the path to a directory where containerd will store transient data
@ -54,6 +59,42 @@ type Config struct {
md toml.MetaData 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 // GRPCConfig provides GRPC configuration for the socket
type GRPCConfig struct { type GRPCConfig struct {
Address string `toml:"address"` Address string `toml:"address"`
@ -130,15 +171,19 @@ func (bc *BoltConfig) Validate() error {
} }
// Decode unmarshals a plugin specific configuration by plugin id // 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] data, ok := c.Plugins[id]
if !ok { 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 nil, err
} }
return v, nil return p.Config, nil
} }
// LoadConfig loads the containerd server config from the provided path // LoadConfig loads the containerd server config from the provided path
@ -151,5 +196,29 @@ func LoadConfig(path string, v *Config) error {
return err return err
} }
v.md = md 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 { if err := sys.MkdirAllWithACL(config.Root, 0711); err != nil {
return err return err
} }
if err := sys.MkdirAllWithACL(config.State, 0711); err != nil {
return err return sys.MkdirAllWithACL(config.State, 0711)
}
return nil
} }
// New creates and initializes a new containerd server // 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 { for _, p := range plugins {
id := p.URI() id := p.URI()
reqID := id
if config.GetVersion() == 1 {
reqID = p.ID
}
log.G(ctx).WithField("type", p.Type).Infof("loading plugin %q...", id) log.G(ctx).WithField("type", p.Type).Infof("loading plugin %q...", id)
initContext := plugin.NewContext( 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 // load the plugin specific configuration if it is provided
if p.Config != nil { if p.Config != nil {
pluginConfig, err := config.Decode(p.ID, p.Config) pc, err := config.Decode(p)
if err != nil { if err != nil {
return nil, err return nil, err
} }
initContext.Config = pluginConfig initContext.Config = pc
} }
result := p.Init(initContext) result := p.Init(initContext)
if err := initialized.Add(result); err != nil { if err := initialized.Add(result); err != nil {
@ -160,12 +162,13 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
} else { } else {
log.G(ctx).WithError(err).Warnf("failed to load plugin %s", id) 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) return nil, errors.Wrapf(err, "load required plugin %s", id)
} }
continue continue
} }
delete(required, p.ID)
delete(required, reqID)
// check for grpc services that should be registered with the server // check for grpc services that should be registered with the server
if src, ok := instance.(plugin.Service); ok { if src, ok := instance.(plugin.Service); ok {
grpcServices = append(grpcServices, src) grpcServices = append(grpcServices, src)
@ -268,7 +271,7 @@ func (s *Server) Stop() {
p := s.plugins[i] p := s.plugins[i]
instance, err := p.Instance() instance, err := p.Instance()
if err != nil { 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") Errorf("could not get plugin instance")
continue continue
} }
@ -277,7 +280,7 @@ func (s *Server) Stop() {
continue continue
} }
if err := closer.Close(); err != nil { 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") 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 the ordered graph for plugins
return plugin.Graph(config.DisabledPlugins), nil return plugin.Graph(filter(config.DisabledPlugins)), nil
} }
type proxyClients struct { type proxyClients struct {
@ -438,7 +445,7 @@ func (pc *proxyClients) getClient(address string) (*grpc.ClientConn, error) {
gopts := []grpc.DialOption{ gopts := []grpc.DialOption{
grpc.WithInsecure(), grpc.WithInsecure(),
grpc.WithBackoffMaxDelay(3 * time.Second), 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. // TODO(stevvooe): We may need to allow configuration of this on the client.
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)),

View File

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

View File

@ -77,3 +77,29 @@ func WithNoPivotRoot(_ context.Context, _ *Client, ti *TaskInfo) error {
} }
return nil 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/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23 github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f
github.com/containerd/cgroups 4994991857f9b0ae8dc439551e8bebdbb4bf66c1 github.com/containerd/cgroups 4994991857f9b0ae8dc439551e8bebdbb4bf66c1
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
@ -8,7 +8,7 @@ github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6 github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098 github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 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/godbus/dbus c7fdd8b5cd55e87b4e1f4e372cdb1db61dd6c66f
github.com/prometheus/client_golang f4fb1b73fb099f396a7f0036bf86aa8def4ed823 github.com/prometheus/client_golang f4fb1b73fb099f396a7f0036bf86aa8def4ed823
github.com/prometheus/client_model 99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c 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/gogo/googleapis v1.2.0
github.com/golang/protobuf v1.2.0 github.com/golang/protobuf v1.2.0
github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db 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/konsorten/go-windows-terminal-sequences v1.0.1
github.com/sirupsen/logrus v1.4.1 github.com/sirupsen/logrus v1.4.1
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac golang.org/x/net f3200d17e092c607f615320ecaad13d87ad9a2b3
google.golang.org/grpc v1.12.0 google.golang.org/grpc 25c4f928eaa6d96443009bd842389fb4fa48664e # v1.20.1
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7 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 github.com/opencontainers/image-spec v1.0.1
golang.org/x/sync 42b317875d0fa942474b76e1b46a6060d720ae6e golang.org/x/sync 42b317875d0fa942474b76e1b46a6060d720ae6e
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.3.1
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0 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 github.com/Microsoft/hcsshim 8abdbb8205e4192c68b5f84c31197156f31be517
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4 golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
github.com/containerd/ttrpc f02858b1457c5ca3aaec3a0803eb0d59f96e41d6 github.com/containerd/ttrpc a5bd8ce9e40bc7c065a11c6936f4d032ce6bfa2b
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16 github.com/syndtr/gocapability d98352740cb2c55f81556b63d4a1ec64c5a319c2
gotest.tools v2.3.0 gotest.tools v2.3.0
github.com/google/go-cmp v0.2.0 github.com/google/go-cmp v0.2.0
go.etcd.io/bbolt v1.3.2 go.etcd.io/bbolt 2eb7227adea1d5cf85f0bc2a82b7059b13c2fa68
# cri dependencies # cri dependencies
github.com/containerd/cri 6d353571e64417d80c9478ffaea793714dd539d0 # master github.com/containerd/cri 2fc62db8146ce66f27b37306ad5fda34207835f3 # master
github.com/containerd/go-cni 40bcf8ec8acd7372be1d77031d585d5d8e561c90 github.com/containerd/go-cni 891c2a41e18144b2d7921f971d6c9789a68046b2
github.com/containernetworking/cni v0.6.0 github.com/containernetworking/cni v0.6.0
github.com/containernetworking/plugins v0.7.0 github.com/containernetworking/plugins v0.7.0
github.com/davecgh/go-spew v1.1.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/json-iterator/go 1.1.5
github.com/modern-go/reflect2 1.0.1 github.com/modern-go/reflect2 1.0.1
github.com/modern-go/concurrent 1.0.3 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/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0
github.com/tchap/go-patricia v2.2.6 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/oauth2 a6bd8cefa1811bd24b86f8902872e4e8225f74c4
golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631 golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
@ -78,8 +78,7 @@ sigs.k8s.io/yaml v1.1.0
# zfs dependencies # zfs dependencies
github.com/containerd/zfs 31af176f2ae84fe142ef2655bf7bb2aa618b3b1f github.com/containerd/zfs 31af176f2ae84fe142ef2655bf7bb2aa618b3b1f
github.com/mistifyio/go-zfs d5b163290a48f624cbf244ebe4e89ce38653064c github.com/mistifyio/go-zfs f784269be439d704d3dfa1906f45dd848fed2beb
github.com/pborman/uuid v1.2.0
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
# aufs dependencies # aufs dependencies

View File

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

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