
When using events, it was found to be fairly unwieldy with a number of extra packages. For the most part, when interacting with the events service, we want types of the same version of the service. This has been accomplished by moving all events types into the events package. In addition, several fixes to the way events are marshaled have been included. Specifically, we defer to the protobuf type registration system to assemble events and type urls, with a little bit sheen on top of add a containerd.io oriented namespace. This has resulted in much cleaner event consumption and has removed the reliance on error prone type urls, in favor of concrete types. Signed-off-by: Stephen J Day <stephen.day@docker.com>
143 lines
3.5 KiB
Go
143 lines
3.5 KiB
Go
package containerd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"syscall"
|
|
|
|
eventsapi "github.com/containerd/containerd/api/services/events/v1"
|
|
"github.com/containerd/containerd/api/services/tasks/v1"
|
|
tasktypes "github.com/containerd/containerd/api/types/task"
|
|
"github.com/containerd/containerd/events"
|
|
protobuf "github.com/gogo/protobuf/types"
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
)
|
|
|
|
type process struct {
|
|
task *task
|
|
|
|
// this is a hack to make a blocking Wait work
|
|
// exec does not have a create/start split so if a quick exiting process like `exit 1`
|
|
// run, the wait does not have enough time to get the pid catch the event. So we need
|
|
// to lock this on process struct create and only unlock it after the pid is set
|
|
// this allow the wait to be called before calling process start and not race with the exit event
|
|
pidSync chan struct{}
|
|
|
|
io *IO
|
|
pid uint32
|
|
spec *specs.Process
|
|
}
|
|
|
|
// Pid returns the pid of the process
|
|
// The pid is not set until start is called and returns
|
|
func (p *process) Pid() uint32 {
|
|
return p.pid
|
|
}
|
|
|
|
// Start starts the exec process
|
|
func (p *process) Start(ctx context.Context) error {
|
|
data, err := json.Marshal(p.spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
request := &tasks.ExecProcessRequest{
|
|
ContainerID: p.task.containerID,
|
|
Terminal: p.io.Terminal,
|
|
Stdin: p.io.Stdin,
|
|
Stdout: p.io.Stdout,
|
|
Stderr: p.io.Stderr,
|
|
Spec: &protobuf.Any{
|
|
TypeUrl: specs.Version,
|
|
Value: data,
|
|
},
|
|
}
|
|
response, err := p.task.client.TaskService().Exec(ctx, request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.pid = response.Pid
|
|
close(p.pidSync)
|
|
return nil
|
|
}
|
|
|
|
func (p *process) Kill(ctx context.Context, s syscall.Signal) error {
|
|
_, err := p.task.client.TaskService().Kill(ctx, &tasks.KillRequest{
|
|
Signal: uint32(s),
|
|
ContainerID: p.task.containerID,
|
|
PidOrAll: &tasks.KillRequest_Pid{
|
|
Pid: p.pid,
|
|
},
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (p *process) Wait(ctx context.Context) (uint32, error) {
|
|
// TODO (ehazlett): add filtering for specific event
|
|
eventstream, err := p.task.client.EventService().Stream(ctx, &eventsapi.StreamEventsRequest{})
|
|
if err != nil {
|
|
return UnknownExitStatus, err
|
|
}
|
|
<-p.pidSync
|
|
evloop:
|
|
for {
|
|
evt, err := eventstream.Recv()
|
|
if err != nil {
|
|
return UnknownExitStatus, err
|
|
}
|
|
|
|
switch {
|
|
case events.Is(evt.Event, &eventsapi.RuntimeEvent{}):
|
|
var e eventsapi.RuntimeEvent
|
|
if err := events.UnmarshalEvent(evt.Event, &e); err != nil {
|
|
return UnknownExitStatus, err
|
|
}
|
|
|
|
if e.Type != tasktypes.Event_EXIT {
|
|
continue evloop
|
|
}
|
|
|
|
if e.ID == p.task.containerID && e.Pid == p.pid {
|
|
return e.ExitStatus, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *process) CloseIO(ctx context.Context, opts ...IOCloserOpts) error {
|
|
r := &tasks.CloseIORequest{
|
|
ContainerID: p.task.containerID,
|
|
Pid: p.pid,
|
|
}
|
|
for _, o := range opts {
|
|
o(r)
|
|
}
|
|
_, err := p.task.client.TaskService().CloseIO(ctx, r)
|
|
return err
|
|
}
|
|
|
|
func (p *process) IO() *IO {
|
|
return p.io
|
|
}
|
|
|
|
func (p *process) Resize(ctx context.Context, w, h uint32) error {
|
|
_, err := p.task.client.TaskService().ResizePty(ctx, &tasks.ResizePtyRequest{
|
|
ContainerID: p.task.containerID,
|
|
Width: w,
|
|
Height: h,
|
|
Pid: p.pid,
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (p *process) Delete(ctx context.Context) (uint32, error) {
|
|
cerr := p.io.Close()
|
|
r, err := p.task.client.TaskService().DeleteProcess(ctx, &tasks.DeleteProcessRequest{
|
|
ContainerID: p.task.containerID,
|
|
Pid: p.pid,
|
|
})
|
|
if err != nil {
|
|
return UnknownExitStatus, err
|
|
}
|
|
return r.ExitStatus, cerr
|
|
}
|