mirror of
https://github.com/containers/podman.git
synced 2025-10-13 09:15:20 +08:00
podman machine ssh: set correct exit code
Forward the ssh exit code to the podman caller. This is useful for scripts. Use the same logic as podman unshare. Fixes #14401 Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
@ -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"
|
||||
@ -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) {
|
||||
|
@ -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"
|
||||
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
@ -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:
|
||||
|
@ -85,6 +85,14 @@ func (ms *machineSession) outputToString() string {
|
||||
return strings.Join(fields, " ")
|
||||
}
|
||||
|
||||
// errorToString returns the error output from a session in string form
|
||||
func (ms *machineSession) errorToString() string {
|
||||
if ms == nil || ms.Err == nil || ms.Err.Contents() == nil {
|
||||
return ""
|
||||
}
|
||||
return string(ms.Err.Contents())
|
||||
}
|
||||
|
||||
// newMB constructor for machine test builders
|
||||
func newMB() (*machineTestBuilder, error) {
|
||||
mb := machineTestBuilder{
|
||||
|
@ -56,5 +56,12 @@ var _ = Describe("podman machine ssh", func() {
|
||||
Expect(err).To(BeNil())
|
||||
Expect(sshSession).To(Exit(0))
|
||||
Expect(sshSession.outputToString()).To(ContainSubstring("Fedora CoreOS"))
|
||||
|
||||
// keep exit code
|
||||
sshSession, err = mb.setName(name).setCmd(ssh.withSSHComand([]string{"false"})).run()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(sshSession).To(Exit(1))
|
||||
Expect(sshSession.outputToString()).To(Equal(""))
|
||||
Expect(sshSession.errorToString()).To(Equal(""))
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user