1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Update daemon code for containerd API changes

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2017-11-29 19:15:20 -05:00
parent 5bd902b5cf
commit aa3ce07c41
12 changed files with 86 additions and 82 deletions

View file

@ -15,7 +15,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/containerd/containerd" "github.com/containerd/containerd/cio"
containertypes "github.com/docker/docker/api/types/container" containertypes "github.com/docker/docker/api/types/container"
mounttypes "github.com/docker/docker/api/types/mount" mounttypes "github.com/docker/docker/api/types/mount"
networktypes "github.com/docker/docker/api/types/network" networktypes "github.com/docker/docker/api/types/network"
@ -1004,7 +1004,7 @@ func (container *Container) CloseStreams() error {
} }
// InitializeStdio is called by libcontainerd to connect the stdio. // InitializeStdio is called by libcontainerd to connect the stdio.
func (container *Container) InitializeStdio(iop *libcontainerd.IOPipe) (containerd.IO, error) { func (container *Container) InitializeStdio(iop *libcontainerd.IOPipe) (cio.IO, error) {
if err := container.startLogging(); err != nil { if err := container.startLogging(); err != nil {
container.Reset(false) container.Reset(false)
return nil, err return nil, err
@ -1020,7 +1020,7 @@ func (container *Container) InitializeStdio(iop *libcontainerd.IOPipe) (containe
} }
} }
return &cio{IO: iop, sc: container.StreamConfig}, nil return &rio{IO: iop, sc: container.StreamConfig}, nil
} }
// SecretMountPath returns the path of the secret mount for the container // SecretMountPath returns the path of the secret mount for the container
@ -1078,19 +1078,19 @@ func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string
return env return env
} }
type cio struct { type rio struct {
containerd.IO cio.IO
sc *stream.Config sc *stream.Config
} }
func (i *cio) Close() error { func (i *rio) Close() error {
i.IO.Close() i.IO.Close()
return i.sc.CloseStreams() return i.sc.CloseStreams()
} }
func (i *cio) Wait() { func (i *rio) Wait() {
i.sc.Wait() i.sc.Wait()
i.IO.Wait() i.IO.Wait()

View file

@ -4,7 +4,7 @@ import (
"runtime" "runtime"
"sync" "sync"
"github.com/containerd/containerd" "github.com/containerd/containerd/cio"
"github.com/docker/docker/container/stream" "github.com/docker/docker/container/stream"
"github.com/docker/docker/libcontainerd" "github.com/docker/docker/libcontainerd"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
@ -43,26 +43,26 @@ func NewConfig() *Config {
} }
} }
type cio struct { type rio struct {
containerd.IO cio.IO
sc *stream.Config sc *stream.Config
} }
func (i *cio) Close() error { func (i *rio) Close() error {
i.IO.Close() i.IO.Close()
return i.sc.CloseStreams() return i.sc.CloseStreams()
} }
func (i *cio) Wait() { func (i *rio) Wait() {
i.sc.Wait() i.sc.Wait()
i.IO.Wait() i.IO.Wait()
} }
// InitializeStdio is called by libcontainerd to connect the stdio. // InitializeStdio is called by libcontainerd to connect the stdio.
func (c *Config) InitializeStdio(iop *libcontainerd.IOPipe) (containerd.IO, error) { func (c *Config) InitializeStdio(iop *libcontainerd.IOPipe) (cio.IO, error) {
c.StreamConfig.CopyToPipe(iop) c.StreamConfig.CopyToPipe(iop)
if c.StreamConfig.Stdin() == nil && !c.Tty && runtime.GOOS == "windows" { if c.StreamConfig.Stdin() == nil && !c.Tty && runtime.GOOS == "windows" {
@ -73,7 +73,7 @@ func (c *Config) InitializeStdio(iop *libcontainerd.IOPipe) (containerd.IO, erro
} }
} }
return &cio{IO: iop, sc: c.StreamConfig}, nil return &rio{IO: iop, sc: c.StreamConfig}, nil
} }
// CloseStreams closes the stdio streams for the exec // CloseStreams closes the stdio streams for the exec

View file

@ -7,7 +7,7 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"github.com/containerd/containerd/linux/runcopts" "github.com/containerd/containerd/linux/runctypes"
"github.com/docker/docker/container" "github.com/docker/docker/container"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -42,7 +42,7 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
if err != nil { if err != nil {
return nil, err return nil, err
} }
opts := &runcopts.RuncOptions{ opts := &runctypes.RuncOptions{
Runtime: path, Runtime: path,
RuntimeRoot: filepath.Join(daemon.configStore.ExecRoot, RuntimeRoot: filepath.Join(daemon.configStore.ExecRoot,
fmt.Sprintf("runtime-%s", container.HostConfig.Runtime)), fmt.Sprintf("runtime-%s", container.HostConfig.Runtime)),

View file

@ -21,12 +21,14 @@ import (
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/containerd/api/events"
eventsapi "github.com/containerd/containerd/api/services/events/v1" eventsapi "github.com/containerd/containerd/api/services/events/v1"
"github.com/containerd/containerd/api/types" "github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/archive" "github.com/containerd/containerd/archive"
"github.com/containerd/containerd/cio"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/linux/runcopts" "github.com/containerd/containerd/linux/runctypes"
"github.com/containerd/typeurl" "github.com/containerd/typeurl"
"github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/ioutils"
"github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/image-spec/specs-go/v1"
@ -70,7 +72,7 @@ func (c *client) Restore(ctx context.Context, id string, attachStdio StdioCallba
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
var cio containerd.IO var rio cio.IO
defer func() { defer func() {
err = wrapError(err) err = wrapError(err)
}() }()
@ -81,20 +83,20 @@ func (c *client) Restore(ctx context.Context, id string, attachStdio StdioCallba
} }
defer func() { defer func() {
if err != nil && cio != nil { if err != nil && rio != nil {
cio.Cancel() rio.Cancel()
cio.Close() rio.Close()
} }
}() }()
t, err := ctr.Task(ctx, func(fifos *containerd.FIFOSet) (containerd.IO, error) { t, err := ctr.Task(ctx, func(fifos *cio.FIFOSet) (cio.IO, error) {
io, err := newIOPipe(fifos) io, err := newIOPipe(fifos)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cio, err = attachStdio(io) rio, err = attachStdio(io)
return cio, err return rio, err
}) })
if err != nil && !strings.Contains(err.Error(), "no running task found") { if err != nil && !strings.Contains(err.Error(), "no running task found") {
return false, -1, err return false, -1, err
@ -168,7 +170,7 @@ func (c *client) Start(ctx context.Context, id, checkpointDir string, withStdin
var ( var (
cp *types.Descriptor cp *types.Descriptor
t containerd.Task t containerd.Task
cio containerd.IO rio cio.IO
err error err error
stdinCloseSync = make(chan struct{}) stdinCloseSync = make(chan struct{})
) )
@ -203,14 +205,14 @@ func (c *client) Start(ctx context.Context, id, checkpointDir string, withStdin
} }
uid, gid := getSpecUser(spec) uid, gid := getSpecUser(spec)
t, err = ctr.ctr.NewTask(ctx, t, err = ctr.ctr.NewTask(ctx,
func(id string) (containerd.IO, error) { func(id string) (cio.IO, error) {
fifos := newFIFOSet(ctr.bundleDir, id, InitProcessName, withStdin, spec.Process.Terminal) fifos := newFIFOSet(ctr.bundleDir, id, InitProcessName, withStdin, spec.Process.Terminal)
cio, err = c.createIO(fifos, id, InitProcessName, stdinCloseSync, attachStdio) rio, err = c.createIO(fifos, id, InitProcessName, stdinCloseSync, attachStdio)
return cio, err return rio, err
}, },
func(_ context.Context, _ *containerd.Client, info *containerd.TaskInfo) error { func(_ context.Context, _ *containerd.Client, info *containerd.TaskInfo) error {
info.Checkpoint = cp info.Checkpoint = cp
info.Options = &runcopts.CreateOptions{ info.Options = &runctypes.CreateOptions{
IoUid: uint32(uid), IoUid: uint32(uid),
IoGid: uint32(gid), IoGid: uint32(gid),
} }
@ -218,9 +220,9 @@ func (c *client) Start(ctx context.Context, id, checkpointDir string, withStdin
}) })
if err != nil { if err != nil {
close(stdinCloseSync) close(stdinCloseSync)
if cio != nil { if rio != nil {
cio.Cancel() rio.Cancel()
cio.Close() rio.Close()
} }
return -1, err return -1, err
} }
@ -259,7 +261,7 @@ func (c *client) Exec(ctx context.Context, containerID, processID string, spec *
var ( var (
p containerd.Process p containerd.Process
cio containerd.IO rio cio.IO
err error err error
stdinCloseSync = make(chan struct{}) stdinCloseSync = make(chan struct{})
) )
@ -268,23 +270,23 @@ func (c *client) Exec(ctx context.Context, containerID, processID string, spec *
defer func() { defer func() {
if err != nil { if err != nil {
if cio != nil { if rio != nil {
cio.Cancel() rio.Cancel()
cio.Close() rio.Close()
} }
rmFIFOSet(fifos) rmFIFOSet(fifos)
} }
}() }()
p, err = ctr.task.Exec(ctx, processID, spec, func(id string) (containerd.IO, error) { p, err = ctr.task.Exec(ctx, processID, spec, func(id string) (cio.IO, error) {
cio, err = c.createIO(fifos, containerID, processID, stdinCloseSync, attachStdio) rio, err = c.createIO(fifos, containerID, processID, stdinCloseSync, attachStdio)
return cio, err return rio, err
}) })
if err != nil { if err != nil {
close(stdinCloseSync) close(stdinCloseSync)
if cio != nil { if rio != nil {
cio.Cancel() rio.Cancel()
cio.Close() rio.Close()
} }
return -1, err return -1, err
} }
@ -569,7 +571,7 @@ func (c *client) getProcess(containerID, processID string) (containerd.Process,
// createIO creates the io to be used by a process // createIO creates the io to be used by a process
// This needs to get a pointer to interface as upon closure the process may not have yet been registered // This needs to get a pointer to interface as upon closure the process may not have yet been registered
func (c *client) createIO(fifos *containerd.FIFOSet, containerID, processID string, stdinCloseSync chan struct{}, attachStdio StdioCallback) (containerd.IO, error) { func (c *client) createIO(fifos *cio.FIFOSet, containerID, processID string, stdinCloseSync chan struct{}, attachStdio StdioCallback) (cio.IO, error) {
io, err := newIOPipe(fifos) io, err := newIOPipe(fifos)
if err != nil { if err != nil {
return nil, err return nil, err
@ -601,12 +603,12 @@ func (c *client) createIO(fifos *containerd.FIFOSet, containerID, processID stri
}) })
} }
cio, err := attachStdio(io) rio, err := attachStdio(io)
if err != nil { if err != nil {
io.Cancel() io.Cancel()
io.Close() io.Close()
} }
return cio, err return rio, err
} }
func (c *client) processEvent(ctr *container, et EventType, ei EventInfo) { func (c *client) processEvent(ctr *container, et EventType, ei EventInfo) {
@ -710,21 +712,21 @@ func (c *client) processEventStream(ctx context.Context) {
c.logger.WithField("topic", ev.Topic).Debug("event") c.logger.WithField("topic", ev.Topic).Debug("event")
switch t := v.(type) { switch t := v.(type) {
case *eventsapi.TaskCreate: case *events.TaskCreate:
et = EventCreate et = EventCreate
ei = EventInfo{ ei = EventInfo{
ContainerID: t.ContainerID, ContainerID: t.ContainerID,
ProcessID: t.ContainerID, ProcessID: t.ContainerID,
Pid: t.Pid, Pid: t.Pid,
} }
case *eventsapi.TaskStart: case *events.TaskStart:
et = EventStart et = EventStart
ei = EventInfo{ ei = EventInfo{
ContainerID: t.ContainerID, ContainerID: t.ContainerID,
ProcessID: t.ContainerID, ProcessID: t.ContainerID,
Pid: t.Pid, Pid: t.Pid,
} }
case *eventsapi.TaskExit: case *events.TaskExit:
et = EventExit et = EventExit
ei = EventInfo{ ei = EventInfo{
ContainerID: t.ContainerID, ContainerID: t.ContainerID,
@ -733,32 +735,32 @@ func (c *client) processEventStream(ctx context.Context) {
ExitCode: t.ExitStatus, ExitCode: t.ExitStatus,
ExitedAt: t.ExitedAt, ExitedAt: t.ExitedAt,
} }
case *eventsapi.TaskOOM: case *events.TaskOOM:
et = EventOOM et = EventOOM
ei = EventInfo{ ei = EventInfo{
ContainerID: t.ContainerID, ContainerID: t.ContainerID,
OOMKilled: true, OOMKilled: true,
} }
oomKilled = true oomKilled = true
case *eventsapi.TaskExecAdded: case *events.TaskExecAdded:
et = EventExecAdded et = EventExecAdded
ei = EventInfo{ ei = EventInfo{
ContainerID: t.ContainerID, ContainerID: t.ContainerID,
ProcessID: t.ExecID, ProcessID: t.ExecID,
} }
case *eventsapi.TaskExecStarted: case *events.TaskExecStarted:
et = EventExecStarted et = EventExecStarted
ei = EventInfo{ ei = EventInfo{
ContainerID: t.ContainerID, ContainerID: t.ContainerID,
ProcessID: t.ExecID, ProcessID: t.ExecID,
Pid: t.Pid, Pid: t.Pid,
} }
case *eventsapi.TaskPaused: case *events.TaskPaused:
et = EventPaused et = EventPaused
ei = EventInfo{ ei = EventInfo{
ContainerID: t.ContainerID, ContainerID: t.ContainerID,
} }
case *eventsapi.TaskResumed: case *events.TaskResumed:
et = EventResumed et = EventResumed
ei = EventInfo{ ei = EventInfo{
ContainerID: t.ContainerID, ContainerID: t.ContainerID,

View file

@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/containerd/cio"
"github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/idtools"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -79,8 +80,8 @@ func prepareBundleDir(bundleDir string, ociSpec *specs.Spec) (string, error) {
return p, nil return p, nil
} }
func newFIFOSet(bundleDir, containerID, processID string, withStdin, withTerminal bool) *containerd.FIFOSet { func newFIFOSet(bundleDir, containerID, processID string, withStdin, withTerminal bool) *cio.FIFOSet {
fifos := &containerd.FIFOSet{ fifos := &cio.FIFOSet{
Terminal: withTerminal, Terminal: withTerminal,
Out: filepath.Join(bundleDir, processID+"-stdout"), Out: filepath.Join(bundleDir, processID+"-stdout"),
} }
@ -96,7 +97,7 @@ func newFIFOSet(bundleDir, containerID, processID string, withStdin, withTermina
return fifos return fifos
} }
func rmFIFOSet(fset *containerd.FIFOSet) { func rmFIFOSet(fset *cio.FIFOSet) {
for _, fn := range []string{fset.Out, fset.In, fset.Err} { for _, fn := range []string{fset.Out, fset.In, fset.Err} {
if fn != "" { if fn != "" {
if err := os.RemoveAll(fn); err != nil { if err := os.RemoveAll(fn); err != nil {

View file

@ -3,7 +3,7 @@ package libcontainerd
import ( import (
"fmt" "fmt"
"github.com/containerd/containerd" "github.com/containerd/containerd/cio"
"github.com/containerd/containerd/windows/hcsshimtypes" "github.com/containerd/containerd/windows/hcsshimtypes"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -35,8 +35,8 @@ func pipeName(containerID, processID, name string) string {
return fmt.Sprintf(`\\.\pipe\containerd-%s-%s-%s`, containerID, processID, name) return fmt.Sprintf(`\\.\pipe\containerd-%s-%s-%s`, containerID, processID, name)
} }
func newFIFOSet(bundleDir, containerID, processID string, withStdin, withTerminal bool) *containerd.FIFOSet { func newFIFOSet(bundleDir, containerID, processID string, withStdin, withTerminal bool) *cio.FIFOSet {
fifos := &containerd.FIFOSet{ fifos := &cio.FIFOSet{
Terminal: withTerminal, Terminal: withTerminal,
Out: pipeName(containerID, processID, "stdout"), Out: pipeName(containerID, processID, "stdout"),
} }

View file

@ -1,9 +1,9 @@
package libcontainerd package libcontainerd
import "github.com/containerd/containerd" import "github.com/containerd/containerd/cio"
// Config returns the containerd.IOConfig of this pipe set // Config returns the containerd.IOConfig of this pipe set
func (p *IOPipe) Config() containerd.IOConfig { func (p *IOPipe) Config() cio.Config {
return p.config return p.config
} }

View file

@ -7,12 +7,12 @@ import (
"io" "io"
"syscall" "syscall"
"github.com/containerd/containerd" "github.com/containerd/containerd/cio"
"github.com/containerd/fifo" "github.com/containerd/fifo"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func newIOPipe(fifos *containerd.FIFOSet) (*IOPipe, error) { func newIOPipe(fifos *cio.FIFOSet) (*IOPipe, error) {
var ( var (
err error err error
ctx, cancel = context.WithCancel(context.Background()) ctx, cancel = context.WithCancel(context.Background())
@ -20,7 +20,7 @@ func newIOPipe(fifos *containerd.FIFOSet) (*IOPipe, error) {
iop = &IOPipe{ iop = &IOPipe{
Terminal: fifos.Terminal, Terminal: fifos.Terminal,
cancel: cancel, cancel: cancel,
config: containerd.IOConfig{ config: cio.Config{
Terminal: fifos.Terminal, Terminal: fifos.Terminal,
Stdin: fifos.In, Stdin: fifos.In,
Stdout: fifos.Out, Stdout: fifos.Out,

View file

@ -7,7 +7,7 @@ import (
"sync" "sync"
winio "github.com/Microsoft/go-winio" winio "github.com/Microsoft/go-winio"
"github.com/containerd/containerd" "github.com/containerd/containerd/cio"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -90,7 +90,7 @@ func (wp *winpipe) Close() error {
} }
} }
func newIOPipe(fifos *containerd.FIFOSet) (*IOPipe, error) { func newIOPipe(fifos *cio.FIFOSet) (*IOPipe, error) {
var ( var (
err error err error
ctx, cancel = context.WithCancel(context.Background()) ctx, cancel = context.WithCancel(context.Background())
@ -98,7 +98,7 @@ func newIOPipe(fifos *containerd.FIFOSet) (*IOPipe, error) {
iop = &IOPipe{ iop = &IOPipe{
Terminal: fifos.Terminal, Terminal: fifos.Terminal,
cancel: cancel, cancel: cancel,
config: containerd.IOConfig{ config: cio.Config{
Terminal: fifos.Terminal, Terminal: fifos.Terminal,
Stdin: fifos.In, Stdin: fifos.In,
Stdout: fifos.Out, Stdout: fifos.Out,

View file

@ -27,7 +27,7 @@ type subreaper bool
func (s subreaper) Apply(r Remote) error { func (s subreaper) Apply(r Remote) error {
if remote, ok := r.(*remote); ok { if remote, ok := r.(*remote); ok {
remote.Subreaper = bool(s) remote.NoSubreaper = !bool(s)
return nil return nil
} }
return fmt.Errorf("WithSubreaper option not supported for this remote") return fmt.Errorf("WithSubreaper option not supported for this remote")

View file

@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/containerd/cio"
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
) )
@ -106,7 +107,7 @@ type Client interface {
} }
// StdioCallback is called to connect a container or process stdio. // StdioCallback is called to connect a container or process stdio.
type StdioCallback func(*IOPipe) (containerd.IO, error) type StdioCallback func(*IOPipe) (cio.IO, error)
// IOPipe contains the stdio streams. // IOPipe contains the stdio streams.
type IOPipe struct { type IOPipe struct {
@ -116,7 +117,7 @@ type IOPipe struct {
Terminal bool // Whether stderr is connected on Windows Terminal bool // Whether stderr is connected on Windows
cancel context.CancelFunc cancel context.CancelFunc
config containerd.IOConfig config cio.Config
} }
// ServerVersion contains version information as retrieved from the // ServerVersion contains version information as retrieved from the

View file

@ -6,8 +6,8 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/containerd/containerd" "github.com/containerd/containerd/cio"
"github.com/containerd/containerd/linux/runcopts" "github.com/containerd/containerd/linux/runctypes"
"github.com/docker/docker/api/errdefs" "github.com/docker/docker/api/errdefs"
"github.com/docker/docker/libcontainerd" "github.com/docker/docker/libcontainerd"
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
@ -46,7 +46,7 @@ type Executor struct {
// Create creates a new container // Create creates a new container
func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error { func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error {
opts := runcopts.RuncOptions{ opts := runctypes.RuncOptions{
RuntimeRoot: filepath.Join(e.rootDir, "runtime-root"), RuntimeRoot: filepath.Join(e.rootDir, "runtime-root"),
} }
ctx := context.Background() ctx := context.Background()
@ -110,37 +110,37 @@ func (e *Executor) ProcessEvent(id string, et libcontainerd.EventType, ei libcon
return nil return nil
} }
type cio struct { type rio struct {
containerd.IO cio.IO
wg sync.WaitGroup wg sync.WaitGroup
} }
func (c *cio) Wait() { func (c *rio) Wait() {
c.wg.Wait() c.wg.Wait()
c.IO.Wait() c.IO.Wait()
} }
func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerd.StdioCallback { func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerd.StdioCallback {
return func(iop *libcontainerd.IOPipe) (containerd.IO, error) { return func(iop *libcontainerd.IOPipe) (cio.IO, error) {
if iop.Stdin != nil { if iop.Stdin != nil {
iop.Stdin.Close() iop.Stdin.Close()
// closing stdin shouldn't be needed here, it should never be open // closing stdin shouldn't be needed here, it should never be open
panic("plugin stdin shouldn't have been created!") panic("plugin stdin shouldn't have been created!")
} }
cio := &cio{IO: iop} rio := &rio{IO: iop}
cio.wg.Add(2) rio.wg.Add(2)
go func() { go func() {
io.Copy(stdout, iop.Stdout) io.Copy(stdout, iop.Stdout)
stdout.Close() stdout.Close()
cio.wg.Done() rio.wg.Done()
}() }()
go func() { go func() {
io.Copy(stderr, iop.Stderr) io.Copy(stderr, iop.Stderr)
stderr.Close() stderr.Close()
cio.wg.Done() rio.wg.Done()
}() }()
return cio, nil return rio, nil
} }
} }