Skip to content

Commit

Permalink
podman machine ssh: set correct exit code
Browse files Browse the repository at this point in the history
Forward the ssh exit code to the podman caller. This is useful for
scripts. USe the same logic as podman unshare.

Fixes containers#14401

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
  • Loading branch information
Luap99 committed May 30, 2022
1 parent a6f8cad commit 3251ad6
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 20 deletions.
4 changes: 3 additions & 1 deletion cmd/podman/machine/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/machine"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -89,7 +90,8 @@ func ssh(cmd *cobra.Command, args []string) error {
if err != nil {
return errors.Wrapf(err, "vm %s not found", vmName)
}
return vm.SSH(vmName, sshOpts)
err = vm.SSH(vmName, sshOpts)
return utils.HandleOSExecError(err)
}

func remoteConnectionUsername() (string, error) {
Expand Down
21 changes: 2 additions & 19 deletions cmd/podman/system/unshare.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package system

import (
"os"
"os/exec"

"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/pkg/errors"
Expand Down Expand Up @@ -60,22 +60,5 @@ func unshare(cmd *cobra.Command, args []string) error {
}

err := registry.ContainerEngine().Unshare(registry.Context(), args, unshareOptions)
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
// the user command inside the unshare env has failed
// we set the exit code, do not return the error to the user
// otherwise "exit status X" will be printed
registry.SetExitCode(exitError.ExitCode())
return nil
}
// cmd.Run() can return fs.ErrNotExist, fs.ErrPermission or exec.ErrNotFound
// follow podman run/exec standard with the exit codes
if errors.Is(err, os.ErrNotExist) || errors.Is(err, exec.ErrNotFound) {
registry.SetExitCode(127)
} else if errors.Is(err, os.ErrPermission) {
registry.SetExitCode(126)
}
return err
}
return nil
return utils.HandleOSExecError(err)
}
32 changes: 32 additions & 0 deletions cmd/podman/utils/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"strconv"
"strings"

buildahCLI "github.com/containers/buildah/pkg/cli"
"github.com/containers/podman/v4/cmd/podman/registry"
)

type OutputErrors []error
Expand Down Expand Up @@ -43,3 +45,33 @@ func ExitCodeFromBuildError(errorMsg string) (int, error) {
}
return buildahCLI.ExecErrorCodeGeneric, errors.New("message does not contains a valid exit code")
}

// HandleOSExecError checks the given error for an exec.ExitError error and
// sets the same podman exit code as the error.
// No error will be returned in this case to make sure things like podman
// unshare false work correctly without extra output.
// When the exec file does not exists we set the exit code to 127, for
// permission errors 126 is used as exit code. In this case we still return
// the error so the user gets an error message.
// If the error is nil it returns nil.
func HandleOSExecError(err error) error {
if err == nil {
return nil
}
var exitError *exec.ExitError
if errors.As(err, &exitError) {
// the user command inside the unshare/ssh env has failed
// we set the exit code, do not return the error to the user
// otherwise "exit status X" will be printed
registry.SetExitCode(exitError.ExitCode())
return nil
}
// cmd.Run() can return fs.ErrNotExist, fs.ErrPermission or exec.ErrNotFound
// follow podman run/exec standard with the exit codes
if errors.Is(err, os.ErrNotExist) || errors.Is(err, exec.ErrNotFound) {
registry.SetExitCode(127)
} else if errors.Is(err, os.ErrPermission) {
registry.SetExitCode(126)
}
return err
}
30 changes: 30 additions & 0 deletions docs/source/markdown/podman-machine-ssh.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ first argument must be the virtual machine name. The optional command to
execute can then follow. If no command is provided, an interactive session
with the virtual machine is established.

The exit code from ssh command will be forwarded to the podman machine ssh caller, see [Exit Codes](#Exit-Codes).

## OPTIONS

Expand All @@ -25,6 +26,35 @@ Print usage statement.

Username to use when SSH-ing into the VM.

## Exit Codes

The exit code from `podman machine ssh` gives information about why the command failed.
When `podman machine ssh` commands exit with a non-zero code,
the exit codes follow the `chroot` standard, see below:

**125** The error is with podman **_itself_**

$ podman machine ssh --foo; echo $?
Error: unknown flag: --foo
125

**126** Executing a _contained command_ and the _command_ cannot be invoked

$ podman machine ssh /etc; echo $?
Error: fork/exec /etc: permission denied
126

**127** Executing a _contained command_ and the _command_ cannot be found

$ podman machine ssh foo; echo $?
Error: fork/exec /usr/bin/bogus: no such file or directory
127

**Exit code** _contained command_ exit code

$ podman machine ssh /bin/sh -c 'exit 3'; echo $?
3

## EXAMPLES

To get an interactive session with the default virtual machine:
Expand Down

0 comments on commit 3251ad6

Please sign in to comment.