Something went wrong. Try again.
dockscope is a TUI for viewing Docker containers, logs, metrics and other info, inspired by lazydocker and dtop.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166package docker
import ( "context" "fmt" "io" "log/slog" "strings"
"github.com/moby/moby/api/pkg/stdcopy" dockerclient "github.com/moby/moby/client" "tangled.org/samatkins.net/dockscope/internal/domain")
const ( shortIDLen = 12
labelComposeProject = "com.docker.compose.project" labelComposeService = "com.docker.compose.service")
type containerLister interface { ContainerList(ctx context.Context, opts dockerclient.ContainerListOptions) (dockerclient.ContainerListResult, error) ContainerStats(ctx context.Context, containerID string, options dockerclient.ContainerStatsOptions) (dockerclient.ContainerStatsResult, error) ContainerLogs(ctx context.Context, containerID string, options dockerclient.ContainerLogsOptions) (dockerclient.ContainerLogsResult, error) ContainerInspect(ctx context.Context, containerID string, options dockerclient.ContainerInspectOptions) (dockerclient.ContainerInspectResult, error) ContainerStop(ctx context.Context, container string, opts dockerclient.ContainerStopOptions) (dockerclient.ContainerStopResult, error) ContainerRestart(ctx context.Context, container string, opts dockerclient.ContainerRestartOptions) (dockerclient.ContainerRestartResult, error) ContainerRemove(ctx context.Context, container string, opts dockerclient.ContainerRemoveOptions) (dockerclient.ContainerRemoveResult, error) ImageList(ctx context.Context, opts dockerclient.ImageListOptions) (dockerclient.ImageListResult, error) VolumeList(ctx context.Context, opts dockerclient.VolumeListOptions) (dockerclient.VolumeListResult, error) NetworkList(ctx context.Context, opts dockerclient.NetworkListOptions) (dockerclient.NetworkListResult, error) ImageHistory(ctx context.Context, imageID string, opts ...dockerclient.ImageHistoryOption) (dockerclient.ImageHistoryResult, error) Close() error}
type liveClient struct { cli containerLister}
func New() (Client, error) { cli, err := dockerclient.New(dockerclient.FromEnv) if err != nil { return nil, fmt.Errorf("docker client: %w", err) }
slog.Debug("connected to Docker daemon")
return &liveClient{cli: cli}, nil}
func (c *liveClient) ListContainers(ctx context.Context) ([]domain.Container, error) { result, err := c.cli.ContainerList(ctx, dockerclient.ContainerListOptions{All: true}) if err != nil { return nil, fmt.Errorf("list containers: %w", err) }
containers := make([]domain.Container, 0, len(result.Items))
for _, ctr := range result.Items { var name string if len(ctr.Names) > 0 { name = strings.TrimPrefix(ctr.Names[0], "/") }
id := ctr.ID if len(id) > shortIDLen { id = id[:shortIDLen] }
containers = append(containers, domain.Container{ ID: id, Name: name, State: domain.ParseContainerState(string(ctr.State)), Status: ctr.Status, Image: ctr.Image, Created: ctr.Created, Project: ctr.Labels[labelComposeProject], Service: ctr.Labels[labelComposeService], }) }
slog.Debug("listed containers", "count", len(containers))
return containers, nil}
func (c *liveClient) StreamLogs(ctx context.Context, containerID, tail string) (io.ReadCloser, error) { info, err := c.cli.ContainerInspect(ctx, containerID, dockerclient.ContainerInspectOptions{}) if err != nil { return nil, fmt.Errorf("inspect container: %w", err) }
tty := info.Container.Config != nil && info.Container.Config.Tty
result, err := c.cli.ContainerLogs(ctx, containerID, dockerclient.ContainerLogsOptions{ ShowStdout: true, ShowStderr: true, Follow: true, Timestamps: true, Tail: tail, }) if err != nil { return nil, fmt.Errorf("container logs: %w", err) }
slog.Debug("streaming container logs", "container", containerID, "tail", tail, "tty", tty)
pr, pw := io.Pipe()
go func() { var copyErr error if tty { _, copyErr = io.Copy(pw, result) } else { _, copyErr = stdcopy.StdCopy(pw, pw, result) }
if closeErr := result.Close(); closeErr != nil { slog.Warn("failed to close log stream", "container", containerID, "error", closeErr) }
// CloseWithError(nil) is equivalent to Close(); the reader sees io.EOF. pw.CloseWithError(copyErr) }()
return pr, nil}
func (c *liveClient) StopContainer(ctx context.Context, containerID string) error { if _, err := c.cli.ContainerStop(ctx, containerID, dockerclient.ContainerStopOptions{}); err != nil { return fmt.Errorf("stop container: %w", err) }
return nil}
func (c *liveClient) RestartContainer(ctx context.Context, containerID string) error { if _, err := c.cli.ContainerRestart(ctx, containerID, dockerclient.ContainerRestartOptions{}); err != nil { return fmt.Errorf("restart container: %w", err) }
return nil}
func (c *liveClient) RemoveContainer(ctx context.Context, containerID string) error { if _, err := c.cli.ContainerRemove(ctx, containerID, dockerclient.ContainerRemoveOptions{ Force: true, RemoveVolumes: false, }); err != nil { return fmt.Errorf("remove container: %w", err) }
return nil}
func (c *liveClient) Close() error { slog.Debug("closing Docker client")
if err := c.cli.Close(); err != nil { return fmt.Errorf("docker close: %w", err) }
return nil}