Skip to content

Commit

Permalink
Podman-remote run should wait for exit code
Browse files Browse the repository at this point in the history
This change matches what is happening on the podman local side
and should eliminate a race condition.

Also exit commands on the server side should start to return to client.
Signed-off-by: Daniel J Walsh <[email protected]>
  • Loading branch information
rhatdan committed Sep 12, 2019
1 parent 535111b commit 82ac0d8
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 29 deletions.
9 changes: 9 additions & 0 deletions libpod/container_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,3 +821,12 @@ func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOpti
defer c.newContainerEvent(events.Restore)
return c.restore(ctx, options)
}

// AutoRemove indicates whether the container will be removed after it is executed
func (c *Container) AutoRemove() bool {
spec := c.config.Spec
if spec.Annotations == nil {
return false
}
return c.Spec().Annotations[InspectAnnotationAutoremove] == InspectResponseTrue
}
9 changes: 1 addition & 8 deletions pkg/adapter/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,14 +396,7 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode
// Do not perform cleanup, or wait for container exit code
// Just exit immediately
if errors.Cause(err) == define.ErrDetach {
exitCode = 0
return exitCode, nil
}
// This means the command did not exist
exitCode = 127
e := strings.ToLower(err.Error())
if strings.Contains(e, "permission denied") || strings.Contains(e, "operation not permitted") {
exitCode = 126
return 0, nil
}
if c.IsSet("rm") {
if deleteError := r.Runtime.RemoveContainer(ctx, ctr, true, false); deleteError != nil {
Expand Down
37 changes: 21 additions & 16 deletions pkg/adapter/containers_remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,19 +464,22 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode
results := shared.NewIntermediateLayer(&c.PodmanCommand, true)
cid, err := iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink())
if err != nil {
return 0, err
return exitCode, err
}
if c.Bool("detach") {
_, err := iopodman.StartContainer().Call(r.Conn, cid)
if _, err := iopodman.StartContainer().Call(r.Conn, cid); err != nil {
return exitCode, err
}
fmt.Println(cid)
return 0, err
return 0, nil
}
errChan, err := r.attach(ctx, os.Stdin, os.Stdout, cid, true, c.String("detach-keys"))
exitChan, errChan, err := r.attach(ctx, os.Stdin, os.Stdout, cid, true, c.String("detach-keys"))
if err != nil {
return 0, err
return exitCode, err
}
exitCode = <-exitChan
finalError := <-errChan
return 0, finalError
return exitCode, finalError
}

func ReadExitFile(runtimeTmp, ctrID string) (int, error) {
Expand Down Expand Up @@ -572,7 +575,7 @@ func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) er
return err
}
}
errChan, err := r.attach(ctx, inputStream, os.Stdout, c.InputArgs[0], false, c.DetachKeys)
_, errChan, err := r.attach(ctx, inputStream, os.Stdout, c.InputArgs[0], false, c.DetachKeys)
if err != nil {
return err
}
Expand Down Expand Up @@ -686,12 +689,13 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP
}
// start.go makes sure that if attach, there can be only one ctr
if c.Attach {
errChan, err := r.attach(ctx, inputStream, os.Stdout, containerIDs[0], true, c.DetachKeys)
exitChan, errChan, err := r.attach(ctx, inputStream, os.Stdout, containerIDs[0], true, c.DetachKeys)
if err != nil {
return exitCode, nil
}
exitCode := <-exitChan
err = <-errChan
return 0, err
return exitCode, err
}

// TODO the notion of starting a pod container and its deps still needs to be worked through
Expand All @@ -710,13 +714,13 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP
return exitCode, finalErr
}

func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool, detachKeys string) (chan error, error) {
func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool, detachKeys string) (chan int, chan error, error) {
var (
oldTermState *term.State
)
spec, err := r.Spec(cid)
if err != nil {
return nil, err
return nil, nil, err
}
resize := make(chan remotecommand.TerminalSize, 5)
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
Expand All @@ -726,7 +730,7 @@ func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid s
if haveTerminal && spec.Process.Terminal {
cancel, oldTermState, err := handleTerminalAttach(ctx, resize)
if err != nil {
return nil, err
return nil, nil, err
}
defer cancel()
defer restoreTerminal(oldTermState)
Expand All @@ -738,19 +742,20 @@ func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid s
reply, err := iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid, detachKeys, start)
if err != nil {
restoreTerminal(oldTermState)
return nil, err
return nil, nil, err
}

// See if the server accepts the upgraded connection or returns an error
_, err = reply()

if err != nil {
restoreTerminal(oldTermState)
return nil, err
return nil, nil, err
}

errChan := configureVarlinkAttachStdio(r.Conn.Reader, r.Conn.Writer, stdin, stdout, oldTermState, resize, nil)
return errChan, nil
ecChan := make(chan int, 1)
errChan := configureVarlinkAttachStdio(r.Conn.Reader, r.Conn.Writer, stdin, stdout, oldTermState, resize, ecChan)
return ecChan, errChan, nil
}

// PauseContainers pauses container(s) based on CLI inputs.
Expand Down
16 changes: 16 additions & 0 deletions pkg/util/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,19 @@ func ValidatePullType(pullType string) (PullType, error) {
return PullImageMissing, errors.Errorf("invalid pull type %q", pullType)
}
}

// ExitCode reads the error message when failing to executing container process
// and then returns 0 if no error, 126 if command does not exist, or 127 for
// all other errors
func ExitCode(err error) int {
if err == nil {
return 0
}
e := strings.ToLower(err.Error())
if strings.Contains(e, "file not found") ||
strings.Contains(e, "no such file or directory") {
return 127
}

return 126
}
29 changes: 28 additions & 1 deletion pkg/varlinkapi/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/varlinkapi/virtwriter"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"k8s.io/client-go/tools/remotecommand"
)
Expand Down Expand Up @@ -79,11 +81,36 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st
finalErr = startAndAttach(ctr, streams, detachKeys, resize, errChan)
}

exitCode := define.ExitCode(finalErr)
if finalErr != define.ErrDetach && finalErr != nil {
logrus.Error(finalErr)
} else {
if ecode, err := ctr.Wait(); err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
// Check events
event, err := i.Runtime.GetLastContainerEvent(ctr.ID(), events.Exited)
if err != nil {
logrus.Errorf("Cannot get exit code: %v", err)
exitCode = define.ExecErrorCodeNotFound
} else {
exitCode = event.ContainerExitCode
}
} else {
exitCode = define.ExitCode(err)
}
} else {
exitCode = int(ecode)
}
}

if ctr.AutoRemove() {
err := i.Runtime.RemoveContainer(getContext(), ctr, false, false)
if err != nil {
logrus.Errorf("Failed to remove container %s: %s", ctr.ID(), err.Error())
}
}

if err = virtwriter.HangUp(writer, 0); err != nil {
if err = virtwriter.HangUp(writer, uint32(exitCode)); err != nil {
logrus.Errorf("Failed to HANG-UP attach to %s: %s", ctr.ID(), err.Error())
}
return call.Writer.Flush()
Expand Down
2 changes: 0 additions & 2 deletions test/e2e/run_exit_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// +build !remoteclient

package integration

import (
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/run_selinux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ var _ = Describe("Podman run", func() {

session = podmanTest.Podman([]string{"run", "-it", "--security-opt", "label=type:spc_t", "--security-opt", "label=filetype:foobar", fedoraMinimal, "ls", "-Z", "/dev"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(127))
Expect(session.ExitCode()).To(Equal(126))
})

})
1 change: 0 additions & 1 deletion test/system/030-run.bats
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ echo $rand | 0 | $rand
# 'run --rm' goes through different code paths and may lose exit status.
# See https://github.com/containers/libpod/issues/3795
@test "podman run --rm" {
skip_if_remote "podman-remote does not handle exit codes"

run_podman 0 run --rm $IMAGE /bin/true
run_podman 1 run --rm $IMAGE /bin/false
Expand Down

0 comments on commit 82ac0d8

Please sign in to comment.