1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
|
package util
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"time"
rspecs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/runtime-tools/specerror"
"github.com/satori/go.uuid"
)
// Runtime represents the basic requirement of a container runtime
type Runtime struct {
RuntimeCommand string
BundleDir string
PidFile string
ID string
stdout *os.File
stderr *os.File
}
// DefaultSignal represents the default signal sends to a container
const DefaultSignal = "TERM"
// NewRuntime create a runtime by command and the bundle directory
func NewRuntime(runtimeCommand string, bundleDir string) (Runtime, error) {
var r Runtime
var err error
r.RuntimeCommand, err = exec.LookPath(runtimeCommand)
if err != nil {
return Runtime{}, err
}
r.BundleDir = bundleDir
return r, err
}
// bundleDir returns the bundle directory. Generally this is
// BundleDir, but when BundleDir is the empty string, it falls back to
// ., as specified in the CLI spec.
func (r *Runtime) bundleDir() (bundleDir string) {
if r.BundleDir == "" {
return "."
}
return r.BundleDir
}
// SetConfig creates a 'config.json' by the generator
func (r *Runtime) SetConfig(g *generate.Generator) error {
if g == nil {
return errors.New("cannot set a nil config")
}
return g.SaveToFile(filepath.Join(r.bundleDir(), "config.json"), generate.ExportOptions{})
}
// SetID sets the container ID
func (r *Runtime) SetID(id string) {
r.ID = id
}
// Create a container
func (r *Runtime) Create() (err error) {
var args []string
args = append(args, "create")
if r.PidFile != "" {
args = append(args, "--pid-file", r.PidFile)
}
if r.BundleDir != "" {
args = append(args, "--bundle", r.BundleDir)
}
if r.ID != "" {
args = append(args, r.ID)
}
cmd := exec.Command(r.RuntimeCommand, args...)
id := uuid.NewV4().String()
r.stdout, err = os.OpenFile(filepath.Join(r.bundleDir(), fmt.Sprintf("stdout-%s", id)), os.O_CREATE|os.O_EXCL|os.O_RDWR, 0600)
if err != nil {
return err
}
cmd.Stdout = r.stdout
r.stderr, err = os.OpenFile(filepath.Join(r.bundleDir(), fmt.Sprintf("stderr-%s", id)), os.O_CREATE|os.O_EXCL|os.O_RDWR, 0600)
if err != nil {
return err
}
cmd.Stderr = r.stderr
err = cmd.Run()
if err == nil {
return err
}
if e, ok := err.(*exec.ExitError); ok {
stdout, stderr, _ := r.ReadStandardStreams()
if len(stderr) == 0 {
stderr = stdout
}
e.Stderr = stderr
return e
}
return err
}
// ReadStandardStreams collects content from the stdout and stderr buffers.
func (r *Runtime) ReadStandardStreams() (stdout []byte, stderr []byte, err error) {
_, err = r.stdout.Seek(0, io.SeekStart)
stdout, err2 := ioutil.ReadAll(r.stdout)
if err == nil && err2 != nil {
err = err2
}
_, err = r.stderr.Seek(0, io.SeekStart)
stderr, err2 = ioutil.ReadAll(r.stderr)
if err == nil && err2 != nil {
err = err2
}
return stdout, stderr, err
}
// Start a container
func (r *Runtime) Start() (err error) {
var args []string
args = append(args, "start")
if r.ID != "" {
args = append(args, r.ID)
}
cmd := exec.Command(r.RuntimeCommand, args...)
return execWithStderrFallbackToStdout(cmd)
}
// State a container information
func (r *Runtime) State() (rspecs.State, error) {
var args []string
args = append(args, "state")
if r.ID != "" {
args = append(args, r.ID)
}
out, err := exec.Command(r.RuntimeCommand, args...).Output()
if err != nil {
if e, ok := err.(*exec.ExitError); ok {
if len(e.Stderr) == 0 {
e.Stderr = out
return rspecs.State{}, e
}
}
return rspecs.State{}, err
}
var state rspecs.State
err = json.Unmarshal(out, &state)
if err != nil {
return rspecs.State{}, specerror.NewError(specerror.DefaultStateJSONPattern, fmt.Errorf("when serialized in JSON, the format MUST adhere to the default pattern"), rspecs.Version)
}
return state, err
}
// Kill a container
func (r *Runtime) Kill(sig string) (err error) {
var args []string
args = append(args, "kill")
if r.ID != "" {
args = append(args, r.ID)
}
if sig != "" {
// TODO: runc does not support this
// args = append(args, "--signal", sig)
args = append(args, sig)
} else {
args = append(args, DefaultSignal)
}
cmd := exec.Command(r.RuntimeCommand, args...)
return execWithStderrFallbackToStdout(cmd)
}
// Delete a container
func (r *Runtime) Delete() (err error) {
var args []string
args = append(args, "delete")
if r.ID != "" {
args = append(args, r.ID)
}
cmd := exec.Command(r.RuntimeCommand, args...)
return execWithStderrFallbackToStdout(cmd)
}
// Clean deletes the container. If removeBundle is set, the bundle
// directory is removed after the container is deleted successfully or, if
// forceRemoveBundle is true, after the deletion attempt regardless of
// whether it was successful or not.
func (r *Runtime) Clean(removeBundle bool, forceRemoveBundle bool) error {
r.Kill("KILL")
WaitingForStatus(*r, LifecycleStatusStopped, time.Second*10, time.Second/10)
err := r.Delete()
if removeBundle && (err == nil || forceRemoveBundle) {
err2 := os.RemoveAll(r.bundleDir())
if err2 != nil && err == nil {
err = err2
}
}
return err
}
func execWithStderrFallbackToStdout(cmd *exec.Cmd) (err error) {
stdout, err := cmd.Output()
if err == nil {
return err
}
if e, ok := err.(*exec.ExitError); ok {
if len(e.Stderr) == 0 {
e.Stderr = stdout
return e
}
}
return err
}
|