Skip to content

Commit 5e8884a

Browse files
committed
libpod: correctly capture healthcheck output
Using the scanner is just unnecessary complicated an buggy as it will not read the final line with a newline. There is also the problem that it happens in a separate goroutine so it could loose output if we read the array before the scanner was done. The API accepts a Writer so we can just directly use a bytes.Buffer which captures all output in memory without the need of another goroutine. This also means that now we always include the final newline in the output. I checked with docker and they do the same so this is good. Fixes #23332 Signed-off-by: Paul Holzinger <[email protected]>
1 parent 8a53e8e commit 5e8884a

File tree

3 files changed

+18
-33
lines changed

3 files changed

+18
-33
lines changed

libpod/healthcheck.go

+5-17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package libpod
44

55
import (
66
"bufio"
7+
"bytes"
78
"context"
89
"errors"
910
"fmt"
@@ -87,30 +88,17 @@ func (c *Container) runHealthCheck(ctx context.Context, isStartup bool) (define.
8788
if len(newCommand) < 1 || newCommand[0] == "" {
8889
return define.HealthCheckNotDefined, "", fmt.Errorf("container %s has no defined healthcheck", c.ID())
8990
}
90-
rPipe, wPipe, err := os.Pipe()
91-
if err != nil {
92-
return define.HealthCheckInternalError, "", fmt.Errorf("unable to create pipe for healthcheck session: %w", err)
93-
}
94-
defer wPipe.Close()
95-
defer rPipe.Close()
9691

9792
streams := new(define.AttachStreams)
93+
output := &bytes.Buffer{}
9894

9995
streams.InputStream = bufio.NewReader(os.Stdin)
100-
streams.OutputStream = wPipe
101-
streams.ErrorStream = wPipe
96+
streams.OutputStream = output
97+
streams.ErrorStream = output
10298
streams.AttachOutput = true
10399
streams.AttachError = true
104100
streams.AttachInput = true
105101

106-
stdout := []string{}
107-
go func() {
108-
scanner := bufio.NewScanner(rPipe)
109-
for scanner.Scan() {
110-
stdout = append(stdout, scanner.Text())
111-
}
112-
}()
113-
114102
logrus.Debugf("executing health check command %s for %s", strings.Join(newCommand, " "), c.ID())
115103
timeStart := time.Now()
116104
hcResult := define.HealthCheckSuccess
@@ -154,7 +142,7 @@ func (c *Container) runHealthCheck(ctx context.Context, isStartup bool) (define.
154142
}
155143
}
156144

157-
eventLog := strings.Join(stdout, "\n")
145+
eventLog := output.String()
158146
if len(eventLog) > MaxHealthCheckLogLength {
159147
eventLog = eventLog[:MaxHealthCheckLogLength]
160148
}

test/e2e/healthcheck_run_test.go

+10-13
Original file line numberDiff line numberDiff line change
@@ -41,25 +41,22 @@ var _ = Describe("Podman healthcheck run", func() {
4141
})
4242

4343
It("podman run healthcheck and logs should contain healthcheck output", func() {
44-
session := podmanTest.Podman([]string{"run", "--name", "test-logs", "-dt", "--health-interval", "1s", "--health-cmd", "echo working", "busybox", "sleep", "3600"})
44+
session := podmanTest.Podman([]string{"run", "--name", "test-logs", "-dt", "--health-interval", "1s",
45+
// echo -n is important for https://github.com/containers/podman/issues/23332
46+
"--health-cmd", "echo -n working", ALPINE, "sleep", "3600"})
4547
session.WaitWithDefaultTimeout()
4648
Expect(session).Should(ExitCleanly())
4749

48-
// Buy a little time to get container running
49-
for i := 0; i < 5; i++ {
50-
hc := podmanTest.Podman([]string{"healthcheck", "run", "test-logs"})
51-
hc.WaitWithDefaultTimeout()
52-
exitCode := hc.ExitCode()
53-
if exitCode == 0 || i == 4 {
54-
break
55-
}
56-
time.Sleep(1 * time.Second)
57-
}
50+
hc := podmanTest.Podman([]string{"healthcheck", "run", "test-logs"})
51+
hc.WaitWithDefaultTimeout()
52+
Expect(hc).Should(ExitCleanly())
5853

59-
hc := podmanTest.Podman([]string{"container", "inspect", "--format", "{{.State.Healthcheck.Log}}", "test-logs"})
54+
// using json formatter here to make sure the newline is not part of the output string an just added by podman inspect
55+
hc = podmanTest.Podman([]string{"container", "inspect", "--format", "{{json (index .State.Healthcheck.Log 0).Output}}", "test-logs"})
6056
hc.WaitWithDefaultTimeout()
6157
Expect(hc).Should(ExitCleanly())
62-
Expect(hc.OutputToString()).To(ContainSubstring("working"))
58+
// exact output match for https://github.com/containers/podman/issues/23332
59+
Expect(string(hc.Out.Contents())).To(Equal("\"working\"\n"))
6360
})
6461

6562
It("podman healthcheck from image's config (not container config)", func() {

test/system/220-healthcheck.bats

+3-3
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function _check_health {
5959
Status | \"healthy\"
6060
FailingStreak | 0
6161
Log[-1].ExitCode | 0
62-
Log[-1].Output | \"Life is Good on stdout\\\nLife is Good on stderr\"
62+
Log[-1].Output | \"Life is Good on stdout\\\nLife is Good on stderr\\\n\"
6363
" "$current_time" "healthy"
6464

6565
current_time=$(date --iso-8601=seconds)
@@ -71,7 +71,7 @@ Log[-1].Output | \"Life is Good on stdout\\\nLife is Good on stderr\"
7171
Status | \"healthy\"
7272
FailingStreak | [123]
7373
Log[-1].ExitCode | 1
74-
Log[-1].Output | \"Uh-oh on stdout!\\\nUh-oh on stderr!\"
74+
Log[-1].Output | \"Uh-oh on stdout!\\\nUh-oh on stderr!\\\n\"
7575
" "$current_time" "healthy"
7676

7777
# Check that we now we do have valid podman units with this
@@ -85,7 +85,7 @@ Log[-1].Output | \"Uh-oh on stdout!\\\nUh-oh on stderr!\"
8585
Status | \"unhealthy\"
8686
FailingStreak | [3456]
8787
Log[-1].ExitCode | 1
88-
Log[-1].Output | \"Uh-oh on stdout!\\\nUh-oh on stderr!\"
88+
Log[-1].Output | \"Uh-oh on stdout!\\\nUh-oh on stderr!\\\n\"
8989
" "$current_time" "unhealthy"
9090

9191
# now the on-failure should kick in and kill the container

0 commit comments

Comments
 (0)