2016-12-16 17:03:35 +00:00
|
|
|
package runc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2017-01-26 18:33:33 +00:00
|
|
|
"path/filepath"
|
2016-12-16 17:03:35 +00:00
|
|
|
"strconv"
|
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Format is the type of log formatting options avaliable
|
|
|
|
type Format string
|
|
|
|
|
|
|
|
const (
|
|
|
|
none Format = ""
|
|
|
|
JSON Format = "json"
|
|
|
|
Text Format = "text"
|
2017-01-20 21:42:14 +00:00
|
|
|
// DefaultCommand is the default command for Runc
|
|
|
|
DefaultCommand string = "runc"
|
2016-12-16 17:03:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Runc is the client to the runc cli
|
|
|
|
type Runc struct {
|
2017-01-20 21:42:14 +00:00
|
|
|
//If command is empty, DefaultCommand is used
|
|
|
|
Command string
|
|
|
|
Root string
|
|
|
|
Debug bool
|
|
|
|
Log string
|
|
|
|
LogFormat Format
|
|
|
|
PdeathSignal syscall.Signal
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// List returns all containers created inside the provided runc root directory
|
|
|
|
func (r *Runc) List(context context.Context) ([]*Container, error) {
|
2017-03-09 21:47:49 +00:00
|
|
|
data, err := Monitor.Output(r.command(context, "list", "--format=json"))
|
2016-12-16 17:03:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var out []*Container
|
|
|
|
if err := json.Unmarshal(data, &out); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// State returns the state for the container provided by id
|
|
|
|
func (r *Runc) State(context context.Context, id string) (*Container, error) {
|
2017-03-09 21:47:49 +00:00
|
|
|
data, err := Monitor.CombinedOutput(r.command(context, "state", id))
|
2016-12-16 17:03:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("%s: %s", err, data)
|
|
|
|
}
|
|
|
|
var c Container
|
|
|
|
if err := json.Unmarshal(data, &c); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &c, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type CreateOpts struct {
|
|
|
|
IO
|
|
|
|
// PidFile is a path to where a pid file should be created
|
2017-01-20 21:42:14 +00:00
|
|
|
PidFile string
|
2017-01-23 19:35:18 +00:00
|
|
|
ConsoleSocket *ConsoleSocket
|
2017-01-20 21:42:14 +00:00
|
|
|
Detach bool
|
|
|
|
NoPivot bool
|
|
|
|
NoNewKeyring bool
|
2017-03-09 21:47:49 +00:00
|
|
|
ExtraFiles []*os.File
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
2017-01-26 18:33:33 +00:00
|
|
|
func (o *CreateOpts) args() (out []string, err error) {
|
2016-12-16 17:03:35 +00:00
|
|
|
if o.PidFile != "" {
|
2017-01-26 18:33:33 +00:00
|
|
|
abs, err := filepath.Abs(o.PidFile)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
out = append(out, "--pid-file", abs)
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
2017-01-23 19:35:18 +00:00
|
|
|
if o.ConsoleSocket != nil {
|
|
|
|
out = append(out, "--console-socket", o.ConsoleSocket.Path())
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
if o.NoPivot {
|
|
|
|
out = append(out, "--no-pivot")
|
|
|
|
}
|
|
|
|
if o.NoNewKeyring {
|
|
|
|
out = append(out, "--no-new-keyring")
|
|
|
|
}
|
|
|
|
if o.Detach {
|
|
|
|
out = append(out, "--detach")
|
|
|
|
}
|
2017-03-09 21:47:49 +00:00
|
|
|
if o.ExtraFiles != nil {
|
|
|
|
out = append(out, "--preserve-fds", strconv.Itoa(len(o.ExtraFiles)))
|
|
|
|
}
|
2017-01-26 18:33:33 +00:00
|
|
|
return out, nil
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create creates a new container and returns its pid if it was created successfully
|
|
|
|
func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOpts) error {
|
|
|
|
args := []string{"create", "--bundle", bundle}
|
|
|
|
if opts != nil {
|
2017-01-26 18:33:33 +00:00
|
|
|
oargs, err := opts.args()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
args = append(args, oargs...)
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
cmd := r.command(context, append(args, id)...)
|
2017-01-26 18:33:33 +00:00
|
|
|
if opts != nil && opts.IO != nil {
|
2017-01-23 22:03:14 +00:00
|
|
|
opts.Set(cmd)
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
2017-03-09 21:47:49 +00:00
|
|
|
cmd.ExtraFiles = opts.ExtraFiles
|
|
|
|
|
2017-01-25 19:52:05 +00:00
|
|
|
if cmd.Stdout == nil && cmd.Stderr == nil {
|
2017-03-09 21:47:49 +00:00
|
|
|
data, err := Monitor.CombinedOutput(cmd)
|
2017-01-25 19:52:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("%s: %s", err, data)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2017-03-09 21:47:49 +00:00
|
|
|
if err := Monitor.Start(cmd); err != nil {
|
2017-01-25 19:52:05 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if opts != nil && opts.IO != nil {
|
|
|
|
if c, ok := opts.IO.(StartCloser); ok {
|
|
|
|
if err := c.CloseAfterStart(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-03-09 21:47:49 +00:00
|
|
|
_, err := Monitor.Wait(cmd)
|
|
|
|
return err
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Start will start an already created container
|
|
|
|
func (r *Runc) Start(context context.Context, id string) error {
|
2017-03-09 21:47:49 +00:00
|
|
|
return r.runOrError(r.command(context, "start", id))
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ExecOpts struct {
|
|
|
|
IO
|
2017-01-20 21:42:14 +00:00
|
|
|
PidFile string
|
2017-01-23 19:35:18 +00:00
|
|
|
ConsoleSocket *ConsoleSocket
|
2017-01-20 21:42:14 +00:00
|
|
|
Detach bool
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
2017-01-26 18:33:33 +00:00
|
|
|
func (o *ExecOpts) args() (out []string, err error) {
|
2017-01-23 19:35:18 +00:00
|
|
|
if o.ConsoleSocket != nil {
|
|
|
|
out = append(out, "--console-socket", o.ConsoleSocket.Path())
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
if o.Detach {
|
|
|
|
out = append(out, "--detach")
|
|
|
|
}
|
|
|
|
if o.PidFile != "" {
|
2017-01-26 18:33:33 +00:00
|
|
|
abs, err := filepath.Abs(o.PidFile)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
out = append(out, "--pid-file", abs)
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
2017-01-26 18:33:33 +00:00
|
|
|
return out, nil
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Exec executres and additional process inside the container based on a full
|
|
|
|
// OCI Process specification
|
|
|
|
func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
|
2017-03-09 21:47:49 +00:00
|
|
|
f, err := ioutil.TempFile("", "runc-process")
|
2016-12-16 17:03:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer os.Remove(f.Name())
|
|
|
|
err = json.NewEncoder(f).Encode(spec)
|
|
|
|
f.Close()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
args := []string{"exec", "--process", f.Name()}
|
|
|
|
if opts != nil {
|
2017-01-26 18:33:33 +00:00
|
|
|
oargs, err := opts.args()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
args = append(args, oargs...)
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
cmd := r.command(context, append(args, id)...)
|
2017-01-26 21:54:48 +00:00
|
|
|
if opts != nil && opts.IO != nil {
|
2017-01-23 22:03:14 +00:00
|
|
|
opts.Set(cmd)
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
2017-01-26 21:54:48 +00:00
|
|
|
if cmd.Stdout == nil && cmd.Stderr == nil {
|
2017-03-09 21:47:49 +00:00
|
|
|
data, err := Monitor.CombinedOutput(cmd)
|
2017-01-26 21:54:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("%s: %s", err, data)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2017-03-09 21:47:49 +00:00
|
|
|
if err := Monitor.Start(cmd); err != nil {
|
2017-01-26 21:54:48 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if opts != nil && opts.IO != nil {
|
|
|
|
if c, ok := opts.IO.(StartCloser); ok {
|
|
|
|
if err := c.CloseAfterStart(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-03-09 21:47:49 +00:00
|
|
|
_, err = Monitor.Wait(cmd)
|
|
|
|
return err
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run runs the create, start, delete lifecycle of the container
|
|
|
|
// and returns its exit status after it has exited
|
|
|
|
func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) {
|
|
|
|
args := []string{"run", "--bundle", bundle}
|
|
|
|
if opts != nil {
|
2017-01-26 18:33:33 +00:00
|
|
|
oargs, err := opts.args()
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
args = append(args, oargs...)
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
cmd := r.command(context, append(args, id)...)
|
|
|
|
if opts != nil {
|
2017-01-23 22:03:14 +00:00
|
|
|
opts.Set(cmd)
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
2017-03-09 21:47:49 +00:00
|
|
|
if err := Monitor.Start(cmd); err != nil {
|
2016-12-16 17:03:35 +00:00
|
|
|
return -1, err
|
|
|
|
}
|
2017-03-09 21:47:49 +00:00
|
|
|
return Monitor.Wait(cmd)
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Delete deletes the container
|
|
|
|
func (r *Runc) Delete(context context.Context, id string) error {
|
2017-03-09 21:47:49 +00:00
|
|
|
return r.runOrError(r.command(context, "delete", id))
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
2017-01-20 21:42:14 +00:00
|
|
|
// KillOpts specifies options for killing a container and its processes
|
|
|
|
type KillOpts struct {
|
|
|
|
All bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *KillOpts) args() (out []string) {
|
|
|
|
if o.All {
|
|
|
|
out = append(out, "--all")
|
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2016-12-16 17:03:35 +00:00
|
|
|
// Kill sends the specified signal to the container
|
2017-01-20 21:42:14 +00:00
|
|
|
func (r *Runc) Kill(context context.Context, id string, sig int, opts *KillOpts) error {
|
|
|
|
args := []string{
|
|
|
|
"kill",
|
|
|
|
}
|
|
|
|
if opts != nil {
|
|
|
|
args = append(args, opts.args()...)
|
|
|
|
}
|
2017-03-09 21:47:49 +00:00
|
|
|
return r.runOrError(r.command(context, append(args, id, strconv.Itoa(sig))...))
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stats return the stats for a container like cpu, memory, and io
|
|
|
|
func (r *Runc) Stats(context context.Context, id string) (*Stats, error) {
|
|
|
|
cmd := r.command(context, "events", "--stats", id)
|
|
|
|
rd, err := cmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
rd.Close()
|
2017-03-09 21:47:49 +00:00
|
|
|
Monitor.Wait(cmd)
|
2016-12-16 17:03:35 +00:00
|
|
|
}()
|
2017-03-09 21:47:49 +00:00
|
|
|
if err := Monitor.Start(cmd); err != nil {
|
2016-12-16 17:03:35 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var e Event
|
|
|
|
if err := json.NewDecoder(rd).Decode(&e); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return e.Stats, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Events returns an event stream from runc for a container with stats and OOM notifications
|
|
|
|
func (r *Runc) Events(context context.Context, id string, interval time.Duration) (chan *Event, error) {
|
|
|
|
cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id)
|
|
|
|
rd, err := cmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-03-09 21:47:49 +00:00
|
|
|
if err := Monitor.Start(cmd); err != nil {
|
2016-12-16 17:03:35 +00:00
|
|
|
rd.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var (
|
|
|
|
dec = json.NewDecoder(rd)
|
|
|
|
c = make(chan *Event, 128)
|
|
|
|
)
|
|
|
|
go func() {
|
|
|
|
defer func() {
|
|
|
|
close(c)
|
|
|
|
rd.Close()
|
2017-03-09 21:47:49 +00:00
|
|
|
Monitor.Wait(cmd)
|
2016-12-16 17:03:35 +00:00
|
|
|
}()
|
|
|
|
for {
|
|
|
|
var e Event
|
|
|
|
if err := dec.Decode(&e); err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
return
|
|
|
|
}
|
2017-01-20 21:42:14 +00:00
|
|
|
e = Event{
|
|
|
|
Type: "error",
|
|
|
|
Err: err,
|
|
|
|
}
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
c <- &e
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pause the container with the provided id
|
|
|
|
func (r *Runc) Pause(context context.Context, id string) error {
|
2017-03-09 21:47:49 +00:00
|
|
|
return r.runOrError(r.command(context, "pause", id))
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Resume the container with the provided id
|
|
|
|
func (r *Runc) Resume(context context.Context, id string) error {
|
2017-03-09 21:47:49 +00:00
|
|
|
return r.runOrError(r.command(context, "resume", id))
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ps lists all the processes inside the container returning their pids
|
|
|
|
func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
|
2017-03-09 21:47:49 +00:00
|
|
|
data, err := Monitor.CombinedOutput(r.command(context, "ps", "--format", "json", id))
|
2016-12-16 17:03:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("%s: %s", err, data)
|
|
|
|
}
|
|
|
|
var pids []int
|
|
|
|
if err := json.Unmarshal(data, &pids); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return pids, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runc) args() (out []string) {
|
|
|
|
if r.Root != "" {
|
|
|
|
out = append(out, "--root", r.Root)
|
|
|
|
}
|
|
|
|
if r.Debug {
|
|
|
|
out = append(out, "--debug")
|
|
|
|
}
|
|
|
|
if r.Log != "" {
|
|
|
|
out = append(out, "--log", r.Log)
|
|
|
|
}
|
|
|
|
if r.LogFormat != none {
|
|
|
|
out = append(out, "--log-format", string(r.LogFormat))
|
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runc) command(context context.Context, args ...string) *exec.Cmd {
|
2017-01-20 21:42:14 +00:00
|
|
|
command := r.Command
|
|
|
|
if command == "" {
|
|
|
|
command = DefaultCommand
|
|
|
|
}
|
|
|
|
cmd := exec.CommandContext(context, command, append(r.args(), args...)...)
|
|
|
|
if r.PdeathSignal != 0 {
|
|
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
|
|
Pdeathsig: r.PdeathSignal,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return cmd
|
2016-12-16 17:03:35 +00:00
|
|
|
}
|
2017-03-09 21:47:49 +00:00
|
|
|
|
|
|
|
// runOrError will run the provided command. If an error is
|
|
|
|
// encountered and neither Stdout or Stderr was set the error and the
|
|
|
|
// stderr of the command will be returned in the format of <error>:
|
|
|
|
// <stderr>
|
|
|
|
func (r *Runc) runOrError(cmd *exec.Cmd) error {
|
|
|
|
if cmd.Stdout != nil || cmd.Stderr != nil {
|
|
|
|
return Monitor.Run(cmd)
|
|
|
|
}
|
|
|
|
data, err := Monitor.CombinedOutput(cmd)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("%s: %s", err, data)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|