Add basic logging to file support
This currently logs to a json file with the stream type. This is slow and hard on the cpu and memory so we need to swich this over to something like protobufs for the binary logs but this is just a start. Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
8469b6d6a4
commit
e5545a1461
12 changed files with 278 additions and 58 deletions
|
@ -1,18 +1,29 @@
|
||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
|
import "github.com/Sirupsen/logrus"
|
||||||
|
|
||||||
type AddProcessEvent struct {
|
type AddProcessEvent struct {
|
||||||
s *Supervisor
|
s *Supervisor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add this to worker for concurrent starts??? maybe not because of races where the container
|
||||||
|
// could be stopped and removed...
|
||||||
func (h *AddProcessEvent) Handle(e *Event) error {
|
func (h *AddProcessEvent) Handle(e *Event) error {
|
||||||
container, ok := h.s.containers[e.ID]
|
container, ok := h.s.containers[e.ID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrContainerNotFound
|
return ErrContainerNotFound
|
||||||
}
|
}
|
||||||
p, err := h.s.runtime.StartProcess(container, *e.Process, e.Stdio)
|
p, io, err := h.s.runtime.StartProcess(container, *e.Process)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := h.s.log(container.Path(), io); err != nil {
|
||||||
|
// log the error but continue with the other commands
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"error": err,
|
||||||
|
"id": e.ID,
|
||||||
|
}).Error("log stdio")
|
||||||
|
}
|
||||||
if e.Pid, err = p.Pid(); err != nil {
|
if e.Pid, err = p.Pid(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,10 +38,6 @@ func (s *apiServer) CreateContainer(ctx context.Context, c *types.CreateContaine
|
||||||
Name: c.Checkpoint,
|
Name: c.Checkpoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
e.Stdio = &runtime.Stdio{
|
|
||||||
Stderr: c.Stderr,
|
|
||||||
Stdout: c.Stdout,
|
|
||||||
}
|
|
||||||
s.sv.SendEvent(e)
|
s.sv.SendEvent(e)
|
||||||
if err := <-e.Err; err != nil {
|
if err := <-e.Err; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
60
ctr/logs.go
Normal file
60
ctr/logs.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/docker/containerd"
|
||||||
|
)
|
||||||
|
|
||||||
|
var LogsCommand = cli.Command{
|
||||||
|
Name: "logs",
|
||||||
|
Usage: "view binary container logs generated by containerd",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "follow,f",
|
||||||
|
Usage: "follow/tail the logs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(context *cli.Context) {
|
||||||
|
path := context.Args().First()
|
||||||
|
if path == "" {
|
||||||
|
fatal("path to the log cannot be empty", 1)
|
||||||
|
}
|
||||||
|
if err := readLogs(path, context.Bool("follow")); err != nil {
|
||||||
|
fatal(err.Error(), 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLogs(path string, follow bool) error {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
dec := json.NewDecoder(f)
|
||||||
|
for {
|
||||||
|
var msg *containerd.Message
|
||||||
|
if err := dec.Decode(&msg); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
if follow {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch msg.Stream {
|
||||||
|
case "stdout":
|
||||||
|
os.Stdout.Write(msg.Data)
|
||||||
|
case "stderr":
|
||||||
|
os.Stderr.Write(msg.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -34,9 +34,10 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
ContainersCommand,
|
|
||||||
CheckpointCommand,
|
CheckpointCommand,
|
||||||
|
ContainersCommand,
|
||||||
EventsCommand,
|
EventsCommand,
|
||||||
|
LogsCommand,
|
||||||
}
|
}
|
||||||
app.Before = func(context *cli.Context) error {
|
app.Before = func(context *cli.Context) error {
|
||||||
if context.GlobalBool("debug") {
|
if context.GlobalBool("debug") {
|
||||||
|
|
1
event.go
1
event.go
|
@ -36,7 +36,6 @@ type Event struct {
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
ID string
|
ID string
|
||||||
BundlePath string
|
BundlePath string
|
||||||
Stdio *runtime.Stdio
|
|
||||||
Pid int
|
Pid int
|
||||||
Status int
|
Status int
|
||||||
Signal os.Signal
|
Signal os.Signal
|
||||||
|
|
|
@ -5,7 +5,6 @@ package linux
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -363,25 +362,29 @@ func (r *libcontainerRuntime) Type() string {
|
||||||
return "libcontainer"
|
return "libcontainer"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *libcontainerRuntime) Create(id, bundlePath string, stdio *runtime.Stdio) (runtime.Container, error) {
|
func (r *libcontainerRuntime) Create(id, bundlePath string) (runtime.Container, *runtime.IO, error) {
|
||||||
spec, rspec, err := r.loadSpec(
|
spec, rspec, err := r.loadSpec(
|
||||||
filepath.Join(bundlePath, "config.json"),
|
filepath.Join(bundlePath, "config.json"),
|
||||||
filepath.Join(bundlePath, "runtime.json"),
|
filepath.Join(bundlePath, "runtime.json"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
config, err := r.createLibcontainerConfig(id, bundlePath, spec, rspec)
|
config, err := r.createLibcontainerConfig(id, bundlePath, spec, rspec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
container, err := r.factory.Create(id, config)
|
container, err := r.factory.Create(id, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create container: %v", err)
|
return nil, nil, fmt.Errorf("create container: %v", err)
|
||||||
}
|
}
|
||||||
process, err := r.newProcess(spec.Process, stdio)
|
process, err := r.newProcess(spec.Process)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
i, err := process.InitializeIO(int(spec.Process.User.UID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
c := &libcontainerContainer{
|
c := &libcontainerContainer{
|
||||||
c: container,
|
c: container,
|
||||||
|
@ -392,20 +395,28 @@ func (r *libcontainerRuntime) Create(id, bundlePath string, stdio *runtime.Stdio
|
||||||
},
|
},
|
||||||
path: bundlePath,
|
path: bundlePath,
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, &runtime.IO{
|
||||||
|
Stdin: i.Stdin,
|
||||||
|
Stdout: i.Stdout,
|
||||||
|
Stderr: i.Stderr,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *libcontainerRuntime) StartProcess(ci runtime.Container, p specs.Process, stdio *runtime.Stdio) (runtime.Process, error) {
|
func (r *libcontainerRuntime) StartProcess(ci runtime.Container, p specs.Process) (runtime.Process, *runtime.IO, error) {
|
||||||
c, ok := ci.(*libcontainerContainer)
|
c, ok := ci.(*libcontainerContainer)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, runtime.ErrInvalidContainerType
|
return nil, nil, runtime.ErrInvalidContainerType
|
||||||
}
|
}
|
||||||
process, err := r.newProcess(p, stdio)
|
process, err := r.newProcess(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
i, err := process.InitializeIO(int(p.User.UID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if err := c.c.Start(process); err != nil {
|
if err := c.c.Start(process); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
lp := &libcontainerProcess{
|
lp := &libcontainerProcess{
|
||||||
process: process,
|
process: process,
|
||||||
|
@ -413,42 +424,29 @@ func (r *libcontainerRuntime) StartProcess(ci runtime.Container, p specs.Process
|
||||||
}
|
}
|
||||||
pid, err := process.Pid()
|
pid, err := process.Pid()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
c.additionalProcesses[pid] = lp
|
c.additionalProcesses[pid] = lp
|
||||||
return lp, nil
|
return lp, &runtime.IO{
|
||||||
|
Stdin: i.Stdin,
|
||||||
|
Stdout: i.Stdout,
|
||||||
|
Stderr: i.Stderr,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newProcess returns a new libcontainer Process with the arguments from the
|
// newProcess returns a new libcontainer Process with the arguments from the
|
||||||
// spec and stdio from the current process.
|
// spec and stdio from the current process.
|
||||||
func (r *libcontainerRuntime) newProcess(p specs.Process, stdio *runtime.Stdio) (*libcontainer.Process, error) {
|
func (r *libcontainerRuntime) newProcess(p specs.Process) (*libcontainer.Process, error) {
|
||||||
var (
|
// TODO: support terminals
|
||||||
stderr, stdout io.Writer
|
if p.Terminal {
|
||||||
)
|
return nil, runtime.ErrTerminalsNotSupported
|
||||||
if stdio != nil {
|
|
||||||
if stdio.Stdout != "" {
|
|
||||||
f, err := os.OpenFile(stdio.Stdout, os.O_CREATE|os.O_WRONLY, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("open stdout: %v", err)
|
|
||||||
}
|
|
||||||
stdout = f
|
|
||||||
}
|
|
||||||
if stdio.Stderr != "" {
|
|
||||||
f, err := os.OpenFile(stdio.Stderr, os.O_CREATE|os.O_WRONLY, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("open stderr: %v", err)
|
|
||||||
}
|
|
||||||
stderr = f
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return &libcontainer.Process{
|
return &libcontainer.Process{
|
||||||
Args: p.Args,
|
Args: p.Args,
|
||||||
Env: p.Env,
|
Env: p.Env,
|
||||||
// TODO: fix libcontainer's API to better support uid/gid in a typesafe way.
|
// TODO: fix libcontainer's API to better support uid/gid in a typesafe way.
|
||||||
User: fmt.Sprintf("%d:%d", p.User.UID, p.User.GID),
|
User: fmt.Sprintf("%d:%d", p.User.UID, p.User.GID),
|
||||||
Cwd: p.Cwd,
|
Cwd: p.Cwd,
|
||||||
Stderr: stderr,
|
|
||||||
Stdout: stdout,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
113
log.go
Normal file
113
log.go
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package containerd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type logConfig struct {
|
||||||
|
BundlePath string
|
||||||
|
LogSize int64 // in bytes
|
||||||
|
Stdin io.WriteCloser
|
||||||
|
Stdout io.ReadCloser
|
||||||
|
Stderr io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogger(i *logConfig) (*logger, error) {
|
||||||
|
l := &logger{
|
||||||
|
config: i,
|
||||||
|
messages: make(chan *Message, DefaultBufferSize),
|
||||||
|
}
|
||||||
|
hout := &logHandler{
|
||||||
|
stream: "stdout",
|
||||||
|
messages: l.messages,
|
||||||
|
}
|
||||||
|
herr := &logHandler{
|
||||||
|
stream: "stderr",
|
||||||
|
messages: l.messages,
|
||||||
|
}
|
||||||
|
l.wg.Add(2)
|
||||||
|
go func() {
|
||||||
|
defer l.wg.Done()
|
||||||
|
io.Copy(hout, i.Stdout)
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer l.wg.Done()
|
||||||
|
io.Copy(herr, i.Stderr)
|
||||||
|
}()
|
||||||
|
return l, l.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Stream string `json:"stream"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
Data []byte `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type logger struct {
|
||||||
|
config *logConfig
|
||||||
|
f *os.File
|
||||||
|
wg sync.WaitGroup
|
||||||
|
messages chan *Message
|
||||||
|
}
|
||||||
|
|
||||||
|
type logHandler struct {
|
||||||
|
stream string
|
||||||
|
messages chan *Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *logHandler) Write(b []byte) (int, error) {
|
||||||
|
h.messages <- &Message{
|
||||||
|
Stream: h.stream,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Data: b,
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) start() error {
|
||||||
|
f, err := os.OpenFile(
|
||||||
|
filepath.Join(l.config.BundlePath, "logs.json"),
|
||||||
|
os.O_CREATE|os.O_WRONLY|os.O_APPEND,
|
||||||
|
0655,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.f = f
|
||||||
|
l.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
l.wg.Done()
|
||||||
|
enc := json.NewEncoder(f)
|
||||||
|
for m := range l.messages {
|
||||||
|
if err := enc.Encode(m); err != nil {
|
||||||
|
logrus.WithField("error", err).Error("write log message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Close() (err error) {
|
||||||
|
for _, c := range []io.Closer{
|
||||||
|
l.config.Stdin,
|
||||||
|
l.config.Stdout,
|
||||||
|
l.config.Stderr,
|
||||||
|
} {
|
||||||
|
if cerr := c.Close(); err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(l.messages)
|
||||||
|
l.wg.Wait()
|
||||||
|
if ferr := l.f.Close(); err == nil {
|
||||||
|
err = ferr
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -24,9 +25,24 @@ type State struct {
|
||||||
Status Status
|
Status Status
|
||||||
}
|
}
|
||||||
|
|
||||||
type Stdio struct {
|
type IO struct {
|
||||||
Stderr string
|
Stdin io.WriteCloser
|
||||||
Stdout string
|
Stdout io.ReadCloser
|
||||||
|
Stderr io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IO) Close() error {
|
||||||
|
var oerr error
|
||||||
|
for _, c := range []io.Closer{
|
||||||
|
i.Stdin,
|
||||||
|
i.Stdout,
|
||||||
|
i.Stderr,
|
||||||
|
} {
|
||||||
|
if err := c.Close(); oerr == nil {
|
||||||
|
oerr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oerr
|
||||||
}
|
}
|
||||||
|
|
||||||
type Stat struct {
|
type Stat struct {
|
||||||
|
|
|
@ -7,18 +7,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNotChildProcess = errors.New("containerd: not a child process for container")
|
ErrNotChildProcess = errors.New("containerd: not a child process for container")
|
||||||
ErrInvalidContainerType = errors.New("containerd: invalid container type for runtime")
|
ErrInvalidContainerType = errors.New("containerd: invalid container type for runtime")
|
||||||
ErrCheckpointNotExists = errors.New("containerd: checkpoint does not exist for container")
|
ErrCheckpointNotExists = errors.New("containerd: checkpoint does not exist for container")
|
||||||
ErrCheckpointExists = errors.New("containerd: checkpoint already exists")
|
ErrCheckpointExists = errors.New("containerd: checkpoint already exists")
|
||||||
ErrContainerExited = errors.New("containerd: container has exited")
|
ErrContainerExited = errors.New("containerd: container has exited")
|
||||||
|
ErrTerminalsNotSupported = errors.New("containerd: terminals are not supported for runtime")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runtime handles containers, containers handle their own actions
|
// Runtime handles containers, containers handle their own actions
|
||||||
type Runtime interface {
|
type Runtime interface {
|
||||||
// Create creates a new container initialized but without it starting it
|
// Type of the runtime
|
||||||
Create(id, bundlePath string, stdio *Stdio) (Container, error)
|
|
||||||
// StartProcess adds a new process to the container
|
|
||||||
StartProcess(Container, specs.Process, *Stdio) (Process, error)
|
|
||||||
Type() string
|
Type() string
|
||||||
|
// Create creates a new container initialized but without it starting it
|
||||||
|
Create(id, bundlePath string) (Container, *IO, error)
|
||||||
|
// StartProcess adds a new process to the container
|
||||||
|
StartProcess(Container, specs.Process) (Process, *IO, error)
|
||||||
}
|
}
|
||||||
|
|
3
start.go
3
start.go
|
@ -5,7 +5,7 @@ type StartEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *StartEvent) Handle(e *Event) error {
|
func (h *StartEvent) Handle(e *Event) error {
|
||||||
container, err := h.s.runtime.Create(e.ID, e.BundlePath, e.Stdio)
|
container, io, err := h.s.runtime.Create(e.ID, e.BundlePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ func (h *StartEvent) Handle(e *Event) error {
|
||||||
ContainersCounter.Inc(1)
|
ContainersCounter.Inc(1)
|
||||||
task := &StartTask{
|
task := &StartTask{
|
||||||
Err: e.Err,
|
Err: e.Err,
|
||||||
|
IO: io,
|
||||||
Container: container,
|
Container: container,
|
||||||
}
|
}
|
||||||
if e.Checkpoint != nil {
|
if e.Checkpoint != nil {
|
||||||
|
|
|
@ -214,3 +214,17 @@ func (s *Supervisor) getContainerForPid(pid int) (runtime.Container, error) {
|
||||||
func (s *Supervisor) SendEvent(evt *Event) {
|
func (s *Supervisor) SendEvent(evt *Event) {
|
||||||
s.events <- evt
|
s.events <- evt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Supervisor) log(path string, i *runtime.IO) error {
|
||||||
|
config := &logConfig{
|
||||||
|
BundlePath: path,
|
||||||
|
Stdin: i.Stdin,
|
||||||
|
Stdout: i.Stdout,
|
||||||
|
Stderr: i.Stderr,
|
||||||
|
}
|
||||||
|
// TODO: save logger to call close after its all done
|
||||||
|
if _, err := newLogger(config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ type Worker interface {
|
||||||
type StartTask struct {
|
type StartTask struct {
|
||||||
Container runtime.Container
|
Container runtime.Container
|
||||||
Checkpoint string
|
Checkpoint string
|
||||||
|
IO *runtime.IO
|
||||||
Err chan error
|
Err chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +34,14 @@ func (w *worker) Start() {
|
||||||
defer w.wg.Done()
|
defer w.wg.Done()
|
||||||
for t := range w.s.tasks {
|
for t := range w.s.tasks {
|
||||||
started := time.Now()
|
started := time.Now()
|
||||||
|
// start logging the container's stdio
|
||||||
|
if err := w.s.log(t.Container.Path(), t.IO); err != nil {
|
||||||
|
evt := NewEvent(DeleteEventType)
|
||||||
|
evt.ID = t.Container.ID()
|
||||||
|
w.s.SendEvent(evt)
|
||||||
|
t.Err <- err
|
||||||
|
continue
|
||||||
|
}
|
||||||
if t.Checkpoint != "" {
|
if t.Checkpoint != "" {
|
||||||
if err := t.Container.Restore(t.Checkpoint); err != nil {
|
if err := t.Container.Restore(t.Checkpoint); err != nil {
|
||||||
evt := NewEvent(DeleteEventType)
|
evt := NewEvent(DeleteEventType)
|
||||||
|
|
Loading…
Add table
Reference in a new issue