diff --git a/libpod/container.go b/libpod/container.go index 2f5376649249..138ab95548b2 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -150,6 +150,8 @@ type ContainerState struct { ExitCode int32 `json:"exitCode,omitempty"` // Exited is whether the container has exited Exited bool `json:"exited,omitempty"` + // The error message produced by a failed container operation + Error string `json:"error,omitempty"` // OOMKilled indicates that the container was killed as it ran out of // memory OOMKilled bool `json:"oomKilled,omitempty"` diff --git a/libpod/container_api.go b/libpod/container_api.go index 35fed93e6447..465e8ae737f4 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -82,21 +82,28 @@ func (c *Container) Init(ctx context.Context, recursive bool) error { // Start requites that all dependency containers (e.g. pod infra containers) be // running before being run. The recursive parameter, if set, will start all // dependencies before starting this container. -func (c *Container) Start(ctx context.Context, recursive bool) error { +func (c *Container) Start(ctx context.Context, recursive bool) (err error) { + defer func() { + if err != nil { + _ = saveContainerError(c, err) + } + }() + if !c.batched { c.lock.Lock() defer c.lock.Unlock() - if err := c.syncContainer(); err != nil { - return err + if err = c.syncContainer(); err != nil { + return } } - if err := c.prepareToStart(ctx, recursive); err != nil { - return err + if err = c.prepareToStart(ctx, recursive); err != nil { + return } // Start the container - return c.start() + err = c.start() + return } // Update updates the given container. @@ -114,18 +121,24 @@ func (c *Container) Update(res *spec.LinuxResources) error { // Attach call occurs before Start). // In overall functionality, it is identical to the Start call, with the added // side effect that an attach session will also be started. -func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachStreams, keys string, resize <-chan resize.TerminalSize, recursive bool) (<-chan error, error) { +func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachStreams, keys string, resize <-chan resize.TerminalSize, recursive bool) (retChan <-chan error, err error) { + defer func() { + if err != nil { + _ = saveContainerError(c, err) + } + }() + if !c.batched { c.lock.Lock() defer c.lock.Unlock() - if err := c.syncContainer(); err != nil { - return nil, err + if err = c.syncContainer(); err != nil { + return } } - if err := c.prepareToStart(ctx, recursive); err != nil { - return nil, err + if err = c.prepareToStart(ctx, recursive); err != nil { + return } attachChan := make(chan error) @@ -146,15 +159,15 @@ func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachSt opts.Start = true opts.Started = startedChan - if err := c.ociRuntime.Attach(c, opts); err != nil { + if err = c.ociRuntime.Attach(c, opts); err != nil { attachChan <- err } close(attachChan) }() select { - case err := <-attachChan: - return nil, err + case err = <-attachChan: + return case <-startedChan: c.newContainerEvent(events.Attach) } @@ -193,25 +206,28 @@ func (c *Container) Stop() error { // StopWithTimeout is a version of Stop that allows a timeout to be specified // manually. If timeout is 0, SIGKILL will be used immediately to kill the // container. -func (c *Container) StopWithTimeout(timeout uint) error { +func (c *Container) StopWithTimeout(timeout uint) (err error) { if !c.batched { c.lock.Lock() defer c.lock.Unlock() - if err := c.syncContainer(); err != nil { - return err + if err = c.syncContainer(); err != nil { + return } } if c.ensureState(define.ContainerStateStopped, define.ContainerStateExited) { - return define.ErrCtrStopped + err = define.ErrCtrStopped + return } if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStateStopping) { - return fmt.Errorf("can only stop created or running containers. %s is in state %s: %w", c.ID(), c.state.State.String(), define.ErrCtrStateInvalid) + err = fmt.Errorf("can only stop created or running containers. %s is in state %s: %w", c.ID(), c.state.State.String(), define.ErrCtrStateInvalid) + return } - return c.stop(timeout) + err = c.stop(timeout) + return } // Kill sends a signal to a container @@ -1035,3 +1051,8 @@ func (c *Container) Stat(ctx context.Context, containerPath string) (*define.Fil info, _, _, err := c.stat(mountPoint, containerPath) return info, err } + +func saveContainerError(c *Container, err error) error { + c.state.Error = err.Error() + return c.save() +} diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 4dc1ca3a5d55..c81ef1348d34 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -129,7 +129,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *define.Driver Pid: runtimeInfo.PID, ConmonPid: runtimeInfo.ConmonPID, ExitCode: runtimeInfo.ExitCode, - Error: "", // can't get yet + Error: runtimeInfo.Error, StartedAt: runtimeInfo.StartedTime, FinishedAt: runtimeInfo.FinishedTime, Checkpointed: runtimeInfo.Checkpointed, diff --git a/test/e2e/inspect_test.go b/test/e2e/inspect_test.go index 1ce2fa93df53..af48b750944c 100644 --- a/test/e2e/inspect_test.go +++ b/test/e2e/inspect_test.go @@ -540,4 +540,20 @@ var _ = Describe("Podman inspect", func() { session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) }) + + It("podman inspect container with bad create args", func() { + session := podmanTest.Podman([]string{"container", "create", ALPINE, "efcho", "Hello World"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + cid := session.OutputToString() + + session = podmanTest.Podman([]string{"start", cid}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(125)) + session = podmanTest.Podman([]string{"container", "inspect", cid, "-f", "'{{ .State.Error }}"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(Not(HaveLen(0))) + }) + })