Skip to content

Commit a994a04

Browse files
Merge pull request #25765 from giuseppe/oci-enoent-errors-v5.4-rhel
[v5.4-rhel] support new crun error messages
2 parents f7bf65c + a7ac20d commit a994a04

File tree

8 files changed

+51
-32
lines changed

8 files changed

+51
-32
lines changed

libpod/define/exec_codes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func ExitCode(err error) int {
4646
e := strings.ToLower(err.Error())
4747
logrus.Debugf("ExitCode msg: %q", e)
4848
if strings.Contains(e, "not found") ||
49+
strings.Contains(e, "executable path is empty") ||
4950
strings.Contains(e, "no such file") {
5051
return ExecErrorCodeNotFound
5152
}

libpod/oci_util.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func getOCIRuntimeError(name, runtimeMsg string) error {
152152
}
153153
return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrOCIRuntimePermissionDenied)
154154
}
155-
if match := regexp.MustCompile("(?i).*executable file not found in.*|.*no such file or directory.*").FindString(runtimeMsg); match != "" {
155+
if match := regexp.MustCompile("(?i).*executable file not found in.*|.*no such file or directory.*|.*open executable.*").FindString(runtimeMsg); match != "" {
156156
errStr := match
157157
if includeFullOutput {
158158
errStr = runtimeMsg

test/e2e/exec_test.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -400,17 +400,14 @@ var _ = Describe("Podman exec", func() {
400400
setup.WaitWithDefaultTimeout()
401401
Expect(setup).Should(ExitCleanly())
402402

403-
expect := "chdir to `/missing`: No such file or directory"
404-
if podmanTest.OCIRuntime == "runc" {
405-
expect = "chdir to cwd"
406-
}
403+
expect := ".*(chdir to cwd|chdir to `/missing`: No such file or directory).*"
407404
session := podmanTest.Podman([]string{"exec", "--workdir", "/missing", "test1", "pwd"})
408405
session.WaitWithDefaultTimeout()
409-
Expect(session).To(ExitWithError(127, expect))
406+
Expect(session).To(ExitWithErrorRegex(127, expect))
410407

411408
session = podmanTest.Podman([]string{"exec", "-w", "/missing", "test1", "pwd"})
412409
session.WaitWithDefaultTimeout()
413-
Expect(session).To(ExitWithError(127, expect))
410+
Expect(session).To(ExitWithErrorRegex(127, expect))
414411
})
415412

416413
It("podman exec cannot be invoked", func() {
@@ -421,19 +418,20 @@ var _ = Describe("Podman exec", func() {
421418
session := podmanTest.Podman([]string{"exec", "test1", "/etc"})
422419
session.WaitWithDefaultTimeout()
423420

424-
// crun (and, we hope, any other future runtimes)
425-
expectedStatus := 126
426-
expectedMessage := "open executable: Operation not permitted: OCI permission denied"
427-
428421
// ...but it's much more complicated under runc (#19552)
429422
if podmanTest.OCIRuntime == "runc" {
430-
expectedMessage = `exec failed: unable to start container process: exec: "/etc": is a directory`
431-
expectedStatus = 255
423+
expectedMessage := `exec failed: unable to start container process: exec: "/etc": is a directory`
424+
expectedStatus := 255
432425
if IsRemote() {
433426
expectedStatus = 125
434427
}
428+
Expect(session).Should(ExitWithError(expectedStatus, expectedMessage))
429+
} else {
430+
// crun (and, we hope, any other future runtimes)
431+
expectedStatus := 126
432+
expectedMessage := ".*(open executable|the path `/etc` is not a regular file): Operation not permitted: OCI permission denied.*"
433+
Expect(session).Should(ExitWithErrorRegex(expectedStatus, expectedMessage))
435434
}
436-
Expect(session).Should(ExitWithError(expectedStatus, expectedMessage))
437435
})
438436

439437
It("podman exec command not found", func() {

test/e2e/run_entrypoint_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ CMD []
1818
podmanTest.BuildImage(dockerfile, "foobar.com/entrypoint:latest", "false")
1919
session := podmanTest.Podman([]string{"run", "foobar.com/entrypoint:latest"})
2020
session.WaitWithDefaultTimeout()
21-
Expect(session).Should(ExitWithError(126, "open executable: Operation not permitted: OCI permission denied"))
21+
if session.ExitCode() == 126 {
22+
// special case for crun <= 1.20, remove once a new version is out
23+
Expect(session).Should(ExitWithError(126, "open executable: Operation not permitted: OCI permission denied"))
24+
return
25+
}
26+
Expect(session).Should(ExitWithErrorRegex(127, ".*(executable file not found in \\$PATH|cannot find `` in \\$PATH).*: OCI runtime attempted to invoke a command that was not found.*"))
2227
})
2328

2429
It("podman run entrypoint == [\"\"]", func() {

test/e2e/run_exit_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ var _ = Describe("Podman run exit", func() {
2222
It("podman run exit ExecErrorCodeCannotInvoke", func() {
2323
result := podmanTest.Podman([]string{"run", ALPINE, "/etc"})
2424
result.WaitWithDefaultTimeout()
25-
Expect(result).Should(ExitWithError(define.ExecErrorCodeCannotInvoke, "open executable: Operation not permitted: OCI permission denied"))
25+
expected := ".*(exec: \"/etc\": is a directory|(open executable|the path `/etc` is not a regular file): Operation not permitted: OCI permission denied).*"
26+
Expect(result).Should(ExitWithErrorRegex(define.ExecErrorCodeCannotInvoke, expected))
2627
})
2728

2829
It("podman run exit ExecErrorCodeNotFound", func() {
2930
result := podmanTest.Podman([]string{"run", ALPINE, "foobar"})
3031
result.WaitWithDefaultTimeout()
31-
Expect(result).Should(ExitWithError(define.ExecErrorCodeNotFound, "executable file `foobar` not found in $PATH: No such file or directory: OCI runtime attempted to invoke a command that was not found"))
32+
expected := ".*(executable file not found in \\$PATH|executable file `foobar` not found in \\$PATH: No such file or directory: OCI runtime attempted to invoke a command that was not found).*"
33+
Expect(result).Should(ExitWithErrorRegex(define.ExecErrorCodeNotFound, expected))
3234
})
3335

3436
It("podman run exit 0", func() {

test/e2e/run_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,12 +1135,6 @@ echo -n madeit-$teststring >$tmpfile
11351135
Expect(session).Should(ExitWithError(125, `invalid stream "asdfasdf" for --attach - must be one of stdin, stdout, or stderr: invalid argument`))
11361136
})
11371137

1138-
It("podman run exit code on failure to exec", func() {
1139-
session := podmanTest.Podman([]string{"run", ALPINE, "/etc"})
1140-
session.WaitWithDefaultTimeout()
1141-
Expect(session).Should(ExitWithError(126, "open executable: Operation not permitted: OCI permission denied"))
1142-
})
1143-
11441138
It("podman run error on exec", func() {
11451139
session := podmanTest.Podman([]string{"run", ALPINE, "sh", "-c", "exit 100"})
11461140
session.WaitWithDefaultTimeout()

test/system/030-run.bats

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ load helpers.network
1010
err_no_such_cmd="Error:.*/no/such/command.*[Nn]o such file or directory"
1111
# runc: RHEL8 on 2023-07-17: "is a directory".
1212
# Everything else (crun; runc on debian): "permission denied"
13-
err_no_exec_dir="Error:.*exec.*\\\(permission denied\\\|is a directory\\\)"
13+
err_no_exec_dir="Error:.*\\\(exec.*\\\(permission denied\\\|is a directory\\\)\\\|is not a regular file\\\)"
1414

1515
tests="
1616
true | 0 |
@@ -1657,14 +1657,14 @@ search | $IMAGE |
16571657
# runc and crun emit different diagnostics
16581658
runtime=$(podman_runtime)
16591659
case "$runtime" in
1660-
crun) expect='crun: executable file `` not found in $PATH: No such file or directory: OCI runtime attempted to invoke a command that was not found' ;;
1660+
crun) expect='\(executable file `` not found in $PATH\|cannot find `` in $PATH\): No such file or directory: OCI runtime attempted to invoke a command that was not found' ;;
16611661
runc) expect='runc: runc create failed: unable to start container process: exec: "": executable file not found in $PATH: OCI runtime attempted to invoke a command that was not found' ;;
16621662
*) skip "Unknown runtime '$runtime'" ;;
16631663
esac
16641664

16651665
# The '.*' in the error below is for dealing with podman-remote, which
16661666
# includes "error preparing container <sha> for attach" in output.
1667-
is "$output" "Error.*: $expect" "podman emits useful diagnostic when no entrypoint is set"
1667+
is "$output" "Error.* $expect" "podman emits useful diagnostic when no entrypoint is set"
16681668
}
16691669

16701670
# bats test_tags=ci:parallel

test/utils/matchers.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package utils
33
import (
44
"encoding/json"
55
"fmt"
6+
"regexp"
67
"strings"
78

89
"github.com/onsi/gomega/format"
@@ -17,10 +18,11 @@ type podmanSession interface {
1718

1819
type ExitMatcher struct {
1920
types.GomegaMatcher
20-
ExpectedExitCode int
21-
ExitCode int
22-
ExpectedStderr string
23-
msg string
21+
ExpectedExitCode int
22+
ExitCode int
23+
ExpectedStderr string
24+
ExpectedStderrRegex string
25+
msg string
2426
}
2527

2628
// ExitWithError checks both exit code and stderr, fails if either does not match
@@ -29,6 +31,12 @@ func ExitWithError(expectExitCode int, expectStderr string) *ExitMatcher {
2931
return &ExitMatcher{ExpectedExitCode: expectExitCode, ExpectedStderr: expectStderr}
3032
}
3133

34+
// ExitWithErrorRegex checks both exit code and the stderr regex, fails if either does not match
35+
// Modeled after the gomega Exit() matcher and also operates on sessions.
36+
func ExitWithErrorRegex(expectExitCode int, expectStderrRegex string) *ExitMatcher {
37+
return &ExitMatcher{ExpectedExitCode: expectExitCode, ExpectedStderrRegex: expectStderrRegex}
38+
}
39+
3240
// Match follows gexec.Matcher interface.
3341
func (matcher *ExitMatcher) Match(actual interface{}) (success bool, err error) {
3442
session, ok := actual.(podmanSession)
@@ -49,12 +57,23 @@ func (matcher *ExitMatcher) Match(actual interface{}) (success bool, err error)
4957
return false, nil
5058
}
5159

52-
if matcher.ExpectedStderr != "" {
60+
switch {
61+
case matcher.ExpectedStderrRegex != "":
62+
matched, err := regexp.MatchString(matcher.ExpectedStderrRegex, session.ErrorToString())
63+
if err != nil {
64+
matcher.msg = fmt.Sprintf("Invalid regex pattern: %s", err)
65+
return false, err
66+
}
67+
if !matched {
68+
matcher.msg = fmt.Sprintf("Command exited %d as expected, but stderr did not match regex '%s'", matcher.ExitCode, matcher.ExpectedStderrRegex)
69+
return false, nil
70+
}
71+
case matcher.ExpectedStderr != "":
5372
if !strings.Contains(session.ErrorToString(), matcher.ExpectedStderr) {
5473
matcher.msg = fmt.Sprintf("Command exited %d as expected, but did not emit '%s'", matcher.ExitCode, matcher.ExpectedStderr)
5574
return false, nil
5675
}
57-
} else {
76+
default:
5877
if session.ErrorToString() != "" {
5978
matcher.msg = "Command exited with expected exit status, but emitted unwanted stderr"
6079
return false, nil

0 commit comments

Comments
 (0)