Fall back to podman logs when journalctl is inaccessible

Probe journalctl with -n 0 before committing to it. When the journal
is not readable (e.g. rootless podman without user journal storage),
fall back to podman logs instead of streaming the permission error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 17:54:14 -07:00
parent bf02935716
commit 3d2edb7c26

View File

@@ -180,36 +180,33 @@ func (p *Podman) Inspect(ctx context.Context, name string) (ContainerInfo, error
} }
// Logs returns an exec.Cmd that streams container logs. For containers // Logs returns an exec.Cmd that streams container logs. For containers
// using the journald log driver, it uses journalctl (podman logs can't // using the journald log driver, it tries journalctl first (podman logs
// read journald outside the originating user session). For k8s-file or // can't read journald outside the originating user session). If journalctl
// other drivers, it uses podman logs directly. // can't access the journal, it falls back to podman logs.
func (p *Podman) Logs(ctx context.Context, containerName string, tail int, follow, timestamps bool, since string) *exec.Cmd { func (p *Podman) Logs(ctx context.Context, containerName string, tail int, follow, timestamps bool, since string) *exec.Cmd {
// Check if this container uses the journald log driver. // Check if this container uses the journald log driver.
inspectCmd := exec.CommandContext(ctx, p.command(), "inspect", "--format", "{{.HostConfig.LogConfig.Type}}", containerName) //nolint:gosec inspectCmd := exec.CommandContext(ctx, p.command(), "inspect", "--format", "{{.HostConfig.LogConfig.Type}}", containerName) //nolint:gosec
if out, err := inspectCmd.Output(); err == nil && strings.TrimSpace(string(out)) == "journald" { if out, err := inspectCmd.Output(); err == nil && strings.TrimSpace(string(out)) == "journald" {
return p.journalLogs(ctx, containerName, tail, follow, since) if p.journalAccessible(ctx, containerName) {
return p.journalLogs(ctx, containerName, tail, follow, since)
}
} }
args := []string{"logs"} return p.podmanLogs(ctx, containerName, tail, follow, timestamps, since)
if tail > 0 { }
args = append(args, "--tail", fmt.Sprintf("%d", tail))
// journalAccessible probes whether journalctl can read logs for the container.
func (p *Podman) journalAccessible(ctx context.Context, containerName string) bool {
args := []string{"--no-pager", "-n", "0"}
if os.Getuid() != 0 {
args = append(args, "--user")
} }
if follow { args = append(args, "CONTAINER_NAME="+containerName)
args = append(args, "--follow") cmd := exec.CommandContext(ctx, "journalctl", args...) //nolint:gosec
} return cmd.Run() == nil
if timestamps {
args = append(args, "--timestamps")
}
if since != "" {
args = append(args, "--since", since)
}
args = append(args, containerName)
return exec.CommandContext(ctx, p.command(), args...) //nolint:gosec // args built programmatically
} }
// journalLogs returns a journalctl command filtered by container name. // journalLogs returns a journalctl command filtered by container name.
// For rootless podman, container logs go to the user journal, so we
// need --user to read them.
func (p *Podman) journalLogs(ctx context.Context, containerName string, tail int, follow bool, since string) *exec.Cmd { func (p *Podman) journalLogs(ctx context.Context, containerName string, tail int, follow bool, since string) *exec.Cmd {
args := []string{"--no-pager", "--output", "cat"} args := []string{"--no-pager", "--output", "cat"}
if os.Getuid() != 0 { if os.Getuid() != 0 {
@@ -228,6 +225,25 @@ func (p *Podman) journalLogs(ctx context.Context, containerName string, tail int
return exec.CommandContext(ctx, "journalctl", args...) //nolint:gosec // args built programmatically return exec.CommandContext(ctx, "journalctl", args...) //nolint:gosec // args built programmatically
} }
// podmanLogs returns a podman logs command.
func (p *Podman) podmanLogs(ctx context.Context, containerName string, tail int, follow, timestamps bool, since string) *exec.Cmd {
args := []string{"logs"}
if tail > 0 {
args = append(args, "--tail", fmt.Sprintf("%d", tail))
}
if follow {
args = append(args, "--follow")
}
if timestamps {
args = append(args, "--timestamps")
}
if since != "" {
args = append(args, "--since", since)
}
args = append(args, containerName)
return exec.CommandContext(ctx, p.command(), args...) //nolint:gosec // args built programmatically
}
// Login authenticates to a container registry using the given token as // Login authenticates to a container registry using the given token as
// the password. This enables non-interactive push with service account // the password. This enables non-interactive push with service account
// tokens (MCR accepts MCIAS JWTs as passwords). // tokens (MCR accepts MCIAS JWTs as passwords).