1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 21:33:18 +01:00

chore: refactors to be more generic (#3594)

This commit is contained in:
Amir Raminfar
2025-02-03 12:42:09 -08:00
committed by GitHub
parent 9f7b17f4ec
commit 5f73b41c57
45 changed files with 504 additions and 503 deletions

View File

@@ -13,7 +13,7 @@ import (
"encoding/json" "encoding/json"
"github.com/amir20/dozzle/internal/agent/pb" "github.com/amir20/dozzle/internal/agent/pb"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/utils" "github.com/amir20/dozzle/internal/utils"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
orderedmap "github.com/wk8/go-ordered-map/v2" orderedmap "github.com/wk8/go-ordered-map/v2"
@@ -89,7 +89,7 @@ func rpcErrToErr(err error) error {
} }
} }
func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since time.Time, until time.Time, std docker.StdType) (<-chan *docker.LogEvent, error) { func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since time.Time, until time.Time, std container.StdType) (<-chan *container.LogEvent, error) {
stream, err := c.client.LogsBetweenDates(ctx, &pb.LogsBetweenDatesRequest{ stream, err := c.client.LogsBetweenDates(ctx, &pb.LogsBetweenDatesRequest{
ContainerId: containerID, ContainerId: containerID,
Since: timestamppb.New(since), Since: timestamppb.New(since),
@@ -101,7 +101,7 @@ func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since
return nil, err return nil, err
} }
events := make(chan *docker.LogEvent) events := make(chan *container.LogEvent)
go func() { go func() {
sendLogs(stream, events) sendLogs(stream, events)
@@ -111,7 +111,7 @@ func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since
return events, nil return events, nil
} }
func (c *Client) StreamContainerLogs(ctx context.Context, containerID string, since time.Time, std docker.StdType, events chan<- *docker.LogEvent) error { func (c *Client) StreamContainerLogs(ctx context.Context, containerID string, since time.Time, std container.StdType, events chan<- *container.LogEvent) error {
stream, err := c.client.StreamLogs(ctx, &pb.StreamLogsRequest{ stream, err := c.client.StreamLogs(ctx, &pb.StreamLogsRequest{
ContainerId: containerID, ContainerId: containerID,
Since: timestamppb.New(since), Since: timestamppb.New(since),
@@ -125,7 +125,7 @@ func (c *Client) StreamContainerLogs(ctx context.Context, containerID string, si
return sendLogs(stream, events) return sendLogs(stream, events)
} }
func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- *docker.LogEvent) error { func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- *container.LogEvent) error {
for { for {
resp, err := stream.Recv() resp, err := stream.Recv()
if err != nil { if err != nil {
@@ -151,19 +151,19 @@ func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- *docker.Log
continue continue
} }
events <- &docker.LogEvent{ events <- &container.LogEvent{
Id: resp.Event.Id, Id: resp.Event.Id,
ContainerID: resp.Event.ContainerId, ContainerID: resp.Event.ContainerId,
Message: message, Message: message,
Timestamp: resp.Event.Timestamp.AsTime().Unix(), Timestamp: resp.Event.Timestamp.AsTime().Unix(),
Position: docker.LogPosition(resp.Event.Position), Position: container.LogPosition(resp.Event.Position),
Level: resp.Event.Level, Level: resp.Event.Level,
Stream: resp.Event.Stream, Stream: resp.Event.Stream,
} }
} }
} }
func (c *Client) StreamRawBytes(ctx context.Context, containerID string, since time.Time, until time.Time, std docker.StdType) (io.ReadCloser, error) { func (c *Client) StreamRawBytes(ctx context.Context, containerID string, since time.Time, until time.Time, std container.StdType) (io.ReadCloser, error) {
out, err := c.client.StreamRawBytes(context.Background(), &pb.StreamRawBytesRequest{ out, err := c.client.StreamRawBytes(context.Background(), &pb.StreamRawBytesRequest{
ContainerId: containerID, ContainerId: containerID,
Since: timestamppb.New(since), Since: timestamppb.New(since),
@@ -199,7 +199,7 @@ func (c *Client) StreamRawBytes(ctx context.Context, containerID string, since t
return r, nil return r, nil
} }
func (c *Client) StreamStats(ctx context.Context, stats chan<- docker.ContainerStat) error { func (c *Client) StreamStats(ctx context.Context, stats chan<- container.ContainerStat) error {
stream, err := c.client.StreamStats(ctx, &pb.StreamStatsRequest{}) stream, err := c.client.StreamStats(ctx, &pb.StreamStatsRequest{})
if err != nil { if err != nil {
return err return err
@@ -211,7 +211,7 @@ func (c *Client) StreamStats(ctx context.Context, stats chan<- docker.ContainerS
return rpcErrToErr(err) return rpcErrToErr(err)
} }
stats <- docker.ContainerStat{ stats <- container.ContainerStat{
CPUPercent: resp.Stat.CpuPercent, CPUPercent: resp.Stat.CpuPercent,
MemoryPercent: resp.Stat.MemoryPercent, MemoryPercent: resp.Stat.MemoryPercent,
MemoryUsage: resp.Stat.MemoryUsage, MemoryUsage: resp.Stat.MemoryUsage,
@@ -220,7 +220,7 @@ func (c *Client) StreamStats(ctx context.Context, stats chan<- docker.ContainerS
} }
} }
func (c *Client) StreamEvents(ctx context.Context, events chan<- docker.ContainerEvent) error { func (c *Client) StreamEvents(ctx context.Context, events chan<- container.ContainerEvent) error {
stream, err := c.client.StreamEvents(ctx, &pb.StreamEventsRequest{}) stream, err := c.client.StreamEvents(ctx, &pb.StreamEventsRequest{})
if err != nil { if err != nil {
return err return err
@@ -232,7 +232,7 @@ func (c *Client) StreamEvents(ctx context.Context, events chan<- docker.Containe
return rpcErrToErr(err) return rpcErrToErr(err)
} }
events <- docker.ContainerEvent{ events <- container.ContainerEvent{
ActorID: resp.Event.ActorId, ActorID: resp.Event.ActorId,
Name: resp.Event.Name, Name: resp.Event.Name,
Host: resp.Event.Host, Host: resp.Event.Host,
@@ -241,7 +241,7 @@ func (c *Client) StreamEvents(ctx context.Context, events chan<- docker.Containe
} }
} }
func (c *Client) StreamNewContainers(ctx context.Context, containers chan<- docker.Container) error { func (c *Client) StreamNewContainers(ctx context.Context, containers chan<- container.Container) error {
stream, err := c.client.StreamContainerStarted(ctx, &pb.StreamContainerStartedRequest{}) stream, err := c.client.StreamContainerStarted(ctx, &pb.StreamContainerStartedRequest{})
if err != nil { if err != nil {
return err return err
@@ -253,7 +253,7 @@ func (c *Client) StreamNewContainers(ctx context.Context, containers chan<- dock
return rpcErrToErr(err) return rpcErrToErr(err)
} }
containers <- docker.Container{ containers <- container.Container{
ID: resp.Container.Id, ID: resp.Container.Id,
Name: resp.Container.Name, Name: resp.Container.Name,
Image: resp.Container.Image, Image: resp.Container.Image,
@@ -271,16 +271,16 @@ func (c *Client) StreamNewContainers(ctx context.Context, containers chan<- dock
} }
} }
func (c *Client) FindContainer(ctx context.Context, containerID string) (docker.Container, error) { func (c *Client) FindContainer(ctx context.Context, containerID string) (container.Container, error) {
response, err := c.client.FindContainer(ctx, &pb.FindContainerRequest{ContainerId: containerID}) response, err := c.client.FindContainer(ctx, &pb.FindContainerRequest{ContainerId: containerID})
if err != nil { if err != nil {
return docker.Container{}, err return container.Container{}, err
} }
var stats []docker.ContainerStat var stats []container.ContainerStat
for _, stat := range response.Container.Stats { for _, stat := range response.Container.Stats {
stats = append(stats, docker.ContainerStat{ stats = append(stats, container.ContainerStat{
ID: stat.Id, ID: stat.Id,
CPUPercent: stat.CpuPercent, CPUPercent: stat.CpuPercent,
MemoryPercent: stat.MemoryPercent, MemoryPercent: stat.MemoryPercent,
@@ -288,7 +288,7 @@ func (c *Client) FindContainer(ctx context.Context, containerID string) (docker.
}) })
} }
return docker.Container{ return container.Container{
ID: response.Container.Id, ID: response.Container.Id,
Name: response.Container.Name, Name: response.Container.Name,
Image: response.Container.Image, Image: response.Container.Image,
@@ -306,7 +306,7 @@ func (c *Client) FindContainer(ctx context.Context, containerID string) (docker.
}, nil }, nil
} }
func (c *Client) ListContainers(ctx context.Context, filter docker.ContainerFilter) ([]docker.Container, error) { func (c *Client) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
in := &pb.ListContainersRequest{} in := &pb.ListContainersRequest{}
if filter != nil { if filter != nil {
@@ -321,11 +321,11 @@ func (c *Client) ListContainers(ctx context.Context, filter docker.ContainerFilt
return nil, err return nil, err
} }
containers := make([]docker.Container, 0) containers := make([]container.Container, 0)
for _, container := range response.Containers { for _, c := range response.Containers {
var stats []docker.ContainerStat var stats []container.ContainerStat
for _, stat := range container.Stats { for _, stat := range c.Stats {
stats = append(stats, docker.ContainerStat{ stats = append(stats, container.ContainerStat{
ID: stat.Id, ID: stat.Id,
CPUPercent: stat.CpuPercent, CPUPercent: stat.CpuPercent,
MemoryPercent: stat.MemoryPercent, MemoryPercent: stat.MemoryPercent,
@@ -333,20 +333,20 @@ func (c *Client) ListContainers(ctx context.Context, filter docker.ContainerFilt
}) })
} }
containers = append(containers, docker.Container{ containers = append(containers, container.Container{
ID: container.Id, ID: c.Id,
Name: container.Name, Name: c.Name,
Image: container.Image, Image: c.Image,
Labels: container.Labels, Labels: c.Labels,
Group: container.Group, Group: c.Group,
Created: container.Created.AsTime(), Created: c.Created.AsTime(),
State: container.State, State: c.State,
Health: container.Health, Health: c.Health,
Host: container.Host, Host: c.Host,
Tty: container.Tty, Tty: c.Tty,
Command: container.Command, Command: c.Command,
StartedAt: container.Started.AsTime(), StartedAt: c.Started.AsTime(),
FinishedAt: container.Finished.AsTime(), FinishedAt: c.Finished.AsTime(),
Stats: utils.RingBufferFrom(300, stats), Stats: utils.RingBufferFrom(300, stats),
}) })
} }
@@ -354,17 +354,17 @@ func (c *Client) ListContainers(ctx context.Context, filter docker.ContainerFilt
return containers, nil return containers, nil
} }
func (c *Client) Host(ctx context.Context) (docker.Host, error) { func (c *Client) Host(ctx context.Context) (container.Host, error) {
info, err := c.client.HostInfo(ctx, &pb.HostInfoRequest{}) info, err := c.client.HostInfo(ctx, &pb.HostInfoRequest{})
if err != nil { if err != nil {
return docker.Host{ return container.Host{
Endpoint: c.endpoint, Endpoint: c.endpoint,
Type: "agent", Type: "agent",
Available: false, Available: false,
}, err }, err
} }
return docker.Host{ return container.Host{
ID: info.Host.Id, ID: info.Host.Id,
Name: info.Host.Name, Name: info.Host.Name,
NCPU: int(info.Host.CpuCores), NCPU: int(info.Host.CpuCores),
@@ -376,16 +376,16 @@ func (c *Client) Host(ctx context.Context) (docker.Host, error) {
}, nil }, nil
} }
func (c *Client) ContainerAction(ctx context.Context, containerId string, action docker.ContainerAction) error { func (c *Client) ContainerAction(ctx context.Context, containerId string, action container.ContainerAction) error {
var containerAction pb.ContainerAction var containerAction pb.ContainerAction
switch action { switch action {
case docker.Start: case container.Start:
containerAction = pb.ContainerAction_Start containerAction = pb.ContainerAction_Start
case docker.Stop: case container.Stop:
containerAction = pb.ContainerAction_Stop containerAction = pb.ContainerAction_Stop
case docker.Restart: case container.Restart:
containerAction = pb.ContainerAction_Restart containerAction = pb.ContainerAction_Restart
} }

View File

@@ -10,7 +10,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/utils" "github.com/amir20/dozzle/internal/utils"
"github.com/docker/docker/api/types/system" "github.com/docker/docker/api/types/system"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -28,46 +28,46 @@ var client *MockedClient
type MockedClient struct { type MockedClient struct {
mock.Mock mock.Mock
docker.Client container.Client
} }
func (m *MockedClient) FindContainer(ctx context.Context, id string) (docker.Container, error) { func (m *MockedClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
args := m.Called(ctx, id) args := m.Called(ctx, id)
return args.Get(0).(docker.Container), args.Error(1) return args.Get(0).(container.Container), args.Error(1)
} }
func (m *MockedClient) ContainerActions(ctx context.Context, action docker.ContainerAction, containerID string) error { func (m *MockedClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error {
args := m.Called(ctx, action, containerID) args := m.Called(ctx, action, containerID)
return args.Error(0) return args.Error(0)
} }
func (m *MockedClient) ContainerEvents(ctx context.Context, events chan<- docker.ContainerEvent) error { func (m *MockedClient) ContainerEvents(ctx context.Context, events chan<- container.ContainerEvent) error {
args := m.Called(ctx, events) args := m.Called(ctx, events)
return args.Error(0) return args.Error(0)
} }
func (m *MockedClient) ListContainers(ctx context.Context, filter docker.ContainerFilter) ([]docker.Container, error) { func (m *MockedClient) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
args := m.Called(ctx, filter) args := m.Called(ctx, filter)
return args.Get(0).([]docker.Container), args.Error(1) return args.Get(0).([]container.Container), args.Error(1)
} }
func (m *MockedClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType docker.StdType) (io.ReadCloser, error) { func (m *MockedClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType container.StdType) (io.ReadCloser, error) {
args := m.Called(ctx, id, since, stdType) args := m.Called(ctx, id, since, stdType)
return args.Get(0).(io.ReadCloser), args.Error(1) return args.Get(0).(io.ReadCloser), args.Error(1)
} }
func (m *MockedClient) ContainerStats(context.Context, string, chan<- docker.ContainerStat) error { func (m *MockedClient) ContainerStats(context.Context, string, chan<- container.ContainerStat) error {
return nil return nil
} }
func (m *MockedClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType docker.StdType) (io.ReadCloser, error) { func (m *MockedClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType container.StdType) (io.ReadCloser, error) {
args := m.Called(ctx, id, from, to, stdType) args := m.Called(ctx, id, from, to, stdType)
return args.Get(0).(io.ReadCloser), args.Error(1) return args.Get(0).(io.ReadCloser), args.Error(1)
} }
func (m *MockedClient) Host() docker.Host { func (m *MockedClient) Host() container.Host {
args := m.Called() args := m.Called()
return args.Get(0).(docker.Host) return args.Get(0).(container.Host)
} }
func (m *MockedClient) IsSwarmMode() bool { func (m *MockedClient) IsSwarmMode() bool {
@@ -92,7 +92,7 @@ func init() {
} }
client = &MockedClient{} client = &MockedClient{}
client.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{ client.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{ {
ID: "123456", ID: "123456",
Name: "test", Name: "test",
@@ -101,17 +101,17 @@ func init() {
}, },
}, nil) }, nil)
client.On("Host").Return(docker.Host{ client.On("Host").Return(container.Host{
ID: "localhost", ID: "localhost",
Endpoint: "local", Endpoint: "local",
Name: "local", Name: "local",
}) })
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) { client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
}) })
client.On("FindContainer", mock.Anything, "123456").Return(docker.Container{ client.On("FindContainer", mock.Anything, "123456").Return(container.Container{
ID: "123456", ID: "123456",
Name: "test", Name: "test",
Host: "localhost", Host: "localhost",
@@ -124,13 +124,13 @@ func init() {
Labels: map[string]string{ Labels: map[string]string{
"test": "test", "test": "test",
}, },
Stats: utils.NewRingBuffer[docker.ContainerStat](300), Stats: utils.NewRingBuffer[container.ContainerStat](300),
Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
StartedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), StartedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
}, nil) }, nil)
server, _ := NewServer(client, certs, "test", docker.ContainerFilter{}) server, _ := NewServer(client, certs, "test", container.ContainerFilter{})
go server.Serve(lis) go server.Serve(lis)
} }
@@ -145,9 +145,9 @@ func TestFindContainer(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
container, _ := rpc.FindContainer(context.Background(), "123456") c, _ := rpc.FindContainer(context.Background(), "123456")
assert.Equal(t, container, docker.Container{ assert.Equal(t, c, container.Container{
ID: "123456", ID: "123456",
Name: "test", Name: "test",
Host: "localhost", Host: "localhost",
@@ -160,7 +160,7 @@ func TestFindContainer(t *testing.T) {
Labels: map[string]string{ Labels: map[string]string{
"test": "test", "test": "test",
}, },
Stats: utils.NewRingBuffer[docker.ContainerStat](300), Stats: utils.NewRingBuffer[container.ContainerStat](300),
Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
StartedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), StartedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
@@ -173,9 +173,9 @@ func TestListContainers(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
containers, _ := rpc.ListContainers(context.Background(), docker.ContainerFilter{}) containers, _ := rpc.ListContainers(context.Background(), container.ContainerFilter{})
assert.Equal(t, containers, []docker.Container{ assert.Equal(t, containers, []container.Container{
{ {
ID: "123456", ID: "123456",
Name: "test", Name: "test",
@@ -189,7 +189,7 @@ func TestListContainers(t *testing.T) {
Labels: map[string]string{ Labels: map[string]string{
"test": "test", "test": "test",
}, },
Stats: utils.NewRingBuffer[docker.ContainerStat](300), Stats: utils.NewRingBuffer[container.ContainerStat](300),
Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
StartedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), StartedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),

View File

@@ -12,7 +12,7 @@ import (
"time" "time"
"github.com/amir20/dozzle/internal/agent/pb" "github.com/amir20/dozzle/internal/agent/pb"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
orderedmap "github.com/wk8/go-ordered-map/v2" orderedmap "github.com/wk8/go-ordered-map/v2"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -25,19 +25,19 @@ import (
) )
type server struct { type server struct {
client docker.Client client container.Client
store *docker.ContainerStore store *container.ContainerStore
version string version string
pb.UnimplementedAgentServiceServer pb.UnimplementedAgentServiceServer
} }
func newServer(client docker.Client, dozzleVersion string, filter docker.ContainerFilter) pb.AgentServiceServer { func newServer(client container.Client, dozzleVersion string, filter container.ContainerFilter) pb.AgentServiceServer {
return &server{ return &server{
client: client, client: client,
version: dozzleVersion, version: dozzleVersion,
store: docker.NewContainerStore(context.Background(), client, filter), store: container.NewContainerStore(context.Background(), client, filter),
} }
} }
@@ -47,17 +47,17 @@ func (s *server) StreamLogs(in *pb.StreamLogsRequest, out pb.AgentService_Stream
since = in.Since.AsTime() since = in.Since.AsTime()
} }
container, err := s.store.FindContainer(in.ContainerId, docker.ContainerFilter{}) c, err := s.store.FindContainer(in.ContainerId, container.ContainerFilter{})
if err != nil { if err != nil {
return err return err
} }
reader, err := s.client.ContainerLogs(out.Context(), in.ContainerId, since, docker.StdType(in.StreamTypes)) reader, err := s.client.ContainerLogs(out.Context(), in.ContainerId, since, container.StdType(in.StreamTypes))
if err != nil { if err != nil {
return err return err
} }
g := docker.NewEventGenerator(out.Context(), reader, container) g := container.NewEventGenerator(out.Context(), reader, c)
for event := range g.Events { for event := range g.Events {
out.Send(&pb.StreamLogsResponse{ out.Send(&pb.StreamLogsResponse{
@@ -74,17 +74,17 @@ func (s *server) StreamLogs(in *pb.StreamLogsRequest, out pb.AgentService_Stream
} }
func (s *server) LogsBetweenDates(in *pb.LogsBetweenDatesRequest, out pb.AgentService_LogsBetweenDatesServer) error { func (s *server) LogsBetweenDates(in *pb.LogsBetweenDatesRequest, out pb.AgentService_LogsBetweenDatesServer) error {
reader, err := s.client.ContainerLogsBetweenDates(out.Context(), in.ContainerId, in.Since.AsTime(), in.Until.AsTime(), docker.StdType(in.StreamTypes)) reader, err := s.client.ContainerLogsBetweenDates(out.Context(), in.ContainerId, in.Since.AsTime(), in.Until.AsTime(), container.StdType(in.StreamTypes))
if err != nil { if err != nil {
return err return err
} }
container, err := s.client.FindContainer(out.Context(), in.ContainerId) c, err := s.client.FindContainer(out.Context(), in.ContainerId)
if err != nil { if err != nil {
return err return err
} }
g := docker.NewEventGenerator(out.Context(), reader, container) g := container.NewEventGenerator(out.Context(), reader, c)
for { for {
select { select {
@@ -101,7 +101,7 @@ func (s *server) LogsBetweenDates(in *pb.LogsBetweenDatesRequest, out pb.AgentSe
} }
func (s *server) StreamRawBytes(in *pb.StreamRawBytesRequest, out pb.AgentService_StreamRawBytesServer) error { func (s *server) StreamRawBytes(in *pb.StreamRawBytesRequest, out pb.AgentService_StreamRawBytesServer) error {
reader, err := s.client.ContainerLogsBetweenDates(out.Context(), in.ContainerId, in.Since.AsTime(), in.Until.AsTime(), docker.StdType(in.StreamTypes)) reader, err := s.client.ContainerLogsBetweenDates(out.Context(), in.ContainerId, in.Since.AsTime(), in.Until.AsTime(), container.StdType(in.StreamTypes))
if err != nil { if err != nil {
return err return err
@@ -129,7 +129,7 @@ func (s *server) StreamRawBytes(in *pb.StreamRawBytesRequest, out pb.AgentServic
} }
func (s *server) StreamEvents(in *pb.StreamEventsRequest, out pb.AgentService_StreamEventsServer) error { func (s *server) StreamEvents(in *pb.StreamEventsRequest, out pb.AgentService_StreamEventsServer) error {
events := make(chan docker.ContainerEvent) events := make(chan container.ContainerEvent)
s.store.SubscribeEvents(out.Context(), events) s.store.SubscribeEvents(out.Context(), events)
@@ -151,7 +151,7 @@ func (s *server) StreamEvents(in *pb.StreamEventsRequest, out pb.AgentService_St
} }
func (s *server) StreamStats(in *pb.StreamStatsRequest, out pb.AgentService_StreamStatsServer) error { func (s *server) StreamStats(in *pb.StreamStatsRequest, out pb.AgentService_StreamStatsServer) error {
stats := make(chan docker.ContainerStat) stats := make(chan container.ContainerStat)
s.store.SubscribeStats(out.Context(), stats) s.store.SubscribeStats(out.Context(), stats)
@@ -173,7 +173,7 @@ func (s *server) StreamStats(in *pb.StreamStatsRequest, out pb.AgentService_Stre
} }
func (s *server) FindContainer(ctx context.Context, in *pb.FindContainerRequest) (*pb.FindContainerResponse, error) { func (s *server) FindContainer(ctx context.Context, in *pb.FindContainerRequest) (*pb.FindContainerResponse, error) {
filter := make(docker.ContainerFilter) filter := make(container.ContainerFilter)
if in.GetFilter() != nil { if in.GetFilter() != nil {
for k, v := range in.GetFilter() { for k, v := range in.GetFilter() {
filter[k] = append(filter[k], v.GetValues()...) filter[k] = append(filter[k], v.GetValues()...)
@@ -205,7 +205,7 @@ func (s *server) FindContainer(ctx context.Context, in *pb.FindContainerRequest)
} }
func (s *server) ListContainers(ctx context.Context, in *pb.ListContainersRequest) (*pb.ListContainersResponse, error) { func (s *server) ListContainers(ctx context.Context, in *pb.ListContainersRequest) (*pb.ListContainersResponse, error) {
filter := make(docker.ContainerFilter) filter := make(container.ContainerFilter)
if in.GetFilter() != nil { if in.GetFilter() != nil {
for k, v := range in.GetFilter() { for k, v := range in.GetFilter() {
filter[k] = append(filter[k], v.GetValues()...) filter[k] = append(filter[k], v.GetValues()...)
@@ -268,7 +268,7 @@ func (s *server) HostInfo(ctx context.Context, in *pb.HostInfoRequest) (*pb.Host
} }
func (s *server) StreamContainerStarted(in *pb.StreamContainerStartedRequest, out pb.AgentService_StreamContainerStartedServer) error { func (s *server) StreamContainerStarted(in *pb.StreamContainerStartedRequest, out pb.AgentService_StreamContainerStartedServer) error {
containers := make(chan docker.Container) containers := make(chan container.Container)
go s.store.SubscribeNewContainers(out.Context(), containers) go s.store.SubscribeNewContainers(out.Context(), containers)
@@ -298,16 +298,16 @@ func (s *server) StreamContainerStarted(in *pb.StreamContainerStartedRequest, ou
} }
func (s *server) ContainerAction(ctx context.Context, in *pb.ContainerActionRequest) (*pb.ContainerActionResponse, error) { func (s *server) ContainerAction(ctx context.Context, in *pb.ContainerActionRequest) (*pb.ContainerActionResponse, error) {
var action docker.ContainerAction var action container.ContainerAction
switch in.Action { switch in.Action {
case pb.ContainerAction_Start: case pb.ContainerAction_Start:
action = docker.Start action = container.Start
case pb.ContainerAction_Stop: case pb.ContainerAction_Stop:
action = docker.Stop action = container.Stop
case pb.ContainerAction_Restart: case pb.ContainerAction_Restart:
action = docker.Restart action = container.Restart
default: default:
return nil, status.Error(codes.InvalidArgument, "invalid action") return nil, status.Error(codes.InvalidArgument, "invalid action")
@@ -322,7 +322,7 @@ func (s *server) ContainerAction(ctx context.Context, in *pb.ContainerActionRequ
return &pb.ContainerActionResponse{}, nil return &pb.ContainerActionResponse{}, nil
} }
func NewServer(client docker.Client, certificates tls.Certificate, dozzleVersion string, filter docker.ContainerFilter) (*grpc.Server, error) { func NewServer(client container.Client, certificates tls.Certificate, dozzleVersion string, filter container.ContainerFilter) (*grpc.Server, error) {
caCertPool := x509.NewCertPool() caCertPool := x509.NewCertPool()
c, err := x509.ParseCertificate(certificates.Certificate[0]) c, err := x509.ParseCertificate(certificates.Certificate[0])
if err != nil { if err != nil {
@@ -346,7 +346,7 @@ func NewServer(client docker.Client, certificates tls.Certificate, dozzleVersion
return grpcServer, nil return grpcServer, nil
} }
func logEventToPb(event *docker.LogEvent) *pb.LogEvent { func logEventToPb(event *container.LogEvent) *pb.LogEvent {
var message *anypb.Any var message *anypb.Any
if event.Message == nil { if event.Message == nil {

View File

@@ -7,7 +7,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -42,7 +42,7 @@ func NewForwardProxyAuth(userHeader, emailHeader, nameHeader, filterHeader strin
func (p *proxyAuthContext) AuthMiddleware(next http.Handler) http.Handler { func (p *proxyAuthContext) AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get(p.headerUser) != "" { if r.Header.Get(p.headerUser) != "" {
containerFilter, err := docker.ParseContainerFilter(r.Header.Get(p.headerFilter)) containerFilter, err := container.ParseContainerFilter(r.Header.Get(p.headerFilter))
if err != nil { if err != nil {
log.Fatal().Str("filter", r.Header.Get(p.headerFilter)).Msg("Failed to parse container filter") log.Fatal().Str("filter", r.Header.Get(p.headerFilter)).Msg("Failed to parse container filter")
} }

View File

@@ -11,7 +11,7 @@ import (
"os" "os"
"time" "time"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/go-chi/jwtauth/v5" "github.com/go-chi/jwtauth/v5"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@@ -19,12 +19,12 @@ import (
) )
type User struct { type User struct {
Username string `json:"username" yaml:"-"` Username string `json:"username" yaml:"-"`
Email string `json:"email" yaml:"email"` Email string `json:"email" yaml:"email"`
Name string `json:"name" yaml:"name"` Name string `json:"name" yaml:"name"`
Password string `json:"-" yaml:"password"` Password string `json:"-" yaml:"password"`
Filter string `json:"-" yaml:"filter"` Filter string `json:"-" yaml:"filter"`
ContainerFilter docker.ContainerFilter `json:"-" yaml:"-"` ContainerFilter container.ContainerFilter `json:"-" yaml:"-"`
} }
func (u User) AvatarURL() string { func (u User) AvatarURL() string {
@@ -35,7 +35,7 @@ func (u User) AvatarURL() string {
return fmt.Sprintf("https://gravatar.com/avatar/%s?d=https%%3A%%2F%%2Fui-avatars.com%%2Fapi%%2F/%s/128", hashEmail(u.Email), url.QueryEscape(name)) return fmt.Sprintf("https://gravatar.com/avatar/%s?d=https%%3A%%2F%%2Fui-avatars.com%%2Fapi%%2F/%s/128", hashEmail(u.Email), url.QueryEscape(name))
} }
func newUser(username, email, name string, filter docker.ContainerFilter) User { func newUser(username, email, name string, filter container.ContainerFilter) User {
return User{ return User{
Username: username, Username: username,
Email: email, Email: email,
@@ -197,9 +197,9 @@ func UserFromContext(ctx context.Context) *User {
} }
email := claims["email"].(string) email := claims["email"].(string)
name := claims["name"].(string) name := claims["name"].(string)
containerFilter := docker.ContainerFilter{} containerFilter := container.ContainerFilter{}
if filter, ok := claims["filter"].(string); ok { if filter, ok := claims["filter"].(string); ok {
containerFilter, err = docker.ParseContainerFilter(filter) containerFilter, err = container.ParseContainerFilter(filter)
if err != nil { if err != nil {
log.Fatal().Err(err).Str("filter", filter).Msg("Failed to parse container filter") log.Fatal().Err(err).Str("filter", filter).Msg("Failed to parse container filter")
} }

View File

@@ -0,0 +1,42 @@
package container
import (
"context"
"io"
"time"
)
type StdType int
const (
UNKNOWN StdType = 1 << iota
STDOUT
STDERR
)
const STDALL = STDOUT | STDERR
func (s StdType) String() string {
switch s {
case STDOUT:
return "stdout"
case STDERR:
return "stderr"
case STDALL:
return "all"
default:
return "unknown"
}
}
type Client interface {
ListContainers(context.Context, ContainerFilter) ([]Container, error)
FindContainer(context.Context, string) (Container, error)
ContainerLogs(context.Context, string, time.Time, StdType) (io.ReadCloser, error)
ContainerEvents(context.Context, chan<- ContainerEvent) error
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time, StdType) (io.ReadCloser, error)
ContainerStats(context.Context, string, chan<- ContainerStat) error
Ping(context.Context) error
Host() Host
ContainerActions(ctx context.Context, action ContainerAction, containerID string) error
IsSwarmMode() bool
}

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"context" "context"

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"context" "context"
@@ -48,7 +48,7 @@ func TestContainerStore_List(t *testing.T) {
Name: "test", Name: "test",
}, },
}, nil) }, nil)
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) { client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
ctx := args.Get(0).(context.Context) ctx := args.Get(0).(context.Context)
<-ctx.Done() <-ctx.Done()
}) })
@@ -83,7 +83,7 @@ func TestContainerStore_die(t *testing.T) {
}, },
}, nil) }, nil)
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil). client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).
Run(func(args mock.Arguments) { Run(func(args mock.Arguments) {
ctx := args.Get(0).(context.Context) ctx := args.Get(0).(context.Context)
events := args.Get(1).(chan<- ContainerEvent) events := args.Get(1).(chan<- ContainerEvent)
@@ -98,7 +98,7 @@ func TestContainerStore_die(t *testing.T) {
ID: "localhost", ID: "localhost",
}) })
client.On("ContainerStats", mock.Anything, "1234", mock.AnythingOfType("chan<- docker.ContainerStat")).Return(nil) client.On("ContainerStats", mock.Anything, "1234", mock.AnythingOfType("chan<- container.ContainerStat")).Return(nil)
client.On("FindContainer", mock.Anything, "1234").Return(Container{ client.On("FindContainer", mock.Anything, "1234").Return(Container{
ID: "1234", ID: "1234",

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"html" "html"
@@ -30,7 +30,6 @@ func escape(logEvent *LogEvent) {
} }
func escapeAnyMap(orderedMap *orderedmap.OrderedMap[string, any]) { func escapeAnyMap(orderedMap *orderedmap.OrderedMap[string, any]) {
for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() { for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() {
switch value := pair.Value.(type) { switch value := pair.Value.(type) {
case string: case string:

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"bufio" "bufio"

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"bufio" "bufio"

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"fmt" "fmt"
@@ -25,6 +25,7 @@ type Host struct {
AgentVersion string `json:"agentVersion,omitempty"` AgentVersion string `json:"agentVersion,omitempty"`
Type string `json:"type"` Type string `json:"type"`
Available bool `json:"available"` Available bool `json:"available"`
Swarm bool `json:"-"`
} }
func (h Host) String() string { func (h Host) String() string {

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"regexp" "regexp"

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"encoding/json" "encoding/json"

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"errors" "errors"

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"encoding/json" "encoding/json"

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"context" "context"

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"context" "context"
@@ -17,13 +17,13 @@ func startedCollector(ctx context.Context) *StatsCollector {
State: "running", State: "running",
}, },
}, nil) }, nil)
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")). client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).
Return(nil). Return(nil).
Run(func(args mock.Arguments) { Run(func(args mock.Arguments) {
ctx := args.Get(0).(context.Context) ctx := args.Get(0).(context.Context)
<-ctx.Done() <-ctx.Done()
}) })
client.On("ContainerStats", mock.Anything, mock.Anything, mock.AnythingOfType("chan<- docker.ContainerStat")). client.On("ContainerStats", mock.Anything, mock.Anything, mock.AnythingOfType("chan<- container.ContainerStat")).
Return(nil). Return(nil).
Run(func(args mock.Arguments) { Run(func(args mock.Arguments) {
stats := args.Get(2).(chan<- ContainerStat) stats := args.Get(2).(chan<- ContainerStat)

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"regexp" "regexp"

View File

@@ -1,4 +1,4 @@
package docker package container
import ( import (
"fmt" "fmt"
@@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/amir20/dozzle/internal/utils" "github.com/amir20/dozzle/internal/utils"
"github.com/docker/docker/api/types/filters"
) )
// Container represents an internal representation of docker containers // Container represents an internal representation of docker containers
@@ -70,17 +69,6 @@ func (f ContainerFilter) Exists() bool {
return len(f) > 0 return len(f) > 0
} }
func (f ContainerFilter) asArgs() filters.Args {
filterArgs := filters.NewArgs()
for key, values := range f {
for _, value := range values {
filterArgs.Add(key, value)
}
}
return filterArgs
}
type LogPosition string type LogPosition string
const ( const (

View File

@@ -11,10 +11,12 @@ import (
"encoding/json" "encoding/json"
"github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/utils" "github.com/amir20/dozzle/internal/utils"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" docker "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system" "github.com/docker/docker/api/types/system"
"github.com/docker/docker/client" "github.com/docker/docker/client"
@@ -22,70 +24,41 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
type StdType int
const (
UNKNOWN StdType = 1 << iota
STDOUT
STDERR
)
const STDALL = STDOUT | STDERR
func (s StdType) String() string {
switch s {
case STDOUT:
return "stdout"
case STDERR:
return "stderr"
case STDALL:
return "all"
default:
return "unknown"
}
}
type DockerCLI interface { type DockerCLI interface {
ContainerList(context.Context, container.ListOptions) ([]types.Container, error) ContainerList(context.Context, docker.ListOptions) ([]types.Container, error)
ContainerLogs(context.Context, string, container.LogsOptions) (io.ReadCloser, error) ContainerLogs(context.Context, string, docker.LogsOptions) (io.ReadCloser, error)
Events(context.Context, events.ListOptions) (<-chan events.Message, <-chan error) Events(context.Context, events.ListOptions) (<-chan events.Message, <-chan error)
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
ContainerStats(ctx context.Context, containerID string, stream bool) (container.StatsResponseReader, error) ContainerStats(ctx context.Context, containerID string, stream bool) (docker.StatsResponseReader, error)
Ping(ctx context.Context) (types.Ping, error) Ping(ctx context.Context) (types.Ping, error)
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error ContainerStart(ctx context.Context, containerID string, options docker.StartOptions) error
ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error ContainerStop(ctx context.Context, containerID string, options docker.StopOptions) error
ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error ContainerRestart(ctx context.Context, containerID string, options docker.StopOptions) error
Info(ctx context.Context) (system.Info, error) Info(ctx context.Context) (system.Info, error)
} }
type Client interface {
ListContainers(context.Context, ContainerFilter) ([]Container, error)
FindContainer(context.Context, string) (Container, error)
ContainerLogs(context.Context, string, time.Time, StdType) (io.ReadCloser, error)
ContainerEvents(context.Context, chan<- ContainerEvent) error
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time, StdType) (io.ReadCloser, error)
ContainerStats(context.Context, string, chan<- ContainerStat) error
Ping(context.Context) (types.Ping, error)
Host() Host
ContainerActions(ctx context.Context, action ContainerAction, containerID string) error
IsSwarmMode() bool
SystemInfo() system.Info
}
type httpClient struct { type httpClient struct {
cli DockerCLI cli DockerCLI
host Host host container.Host
info system.Info info system.Info
} }
func NewClient(cli DockerCLI, host Host) Client { func NewClient(cli DockerCLI, host container.Host) container.Client {
info, err := cli.Info(context.Background()) info, err := cli.Info(context.Background())
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to get docker info") log.Error().Err(err).Msg("Failed to get docker info")
} }
id := info.ID
if info.Swarm.NodeID != "" {
id = info.Swarm.NodeID
}
host.ID = id
host.NCPU = info.NCPU host.NCPU = info.NCPU
host.MemTotal = info.MemTotal host.MemTotal = info.MemTotal
host.DockerVersion = info.ServerVersion host.DockerVersion = info.ServerVersion
host.Swarm = info.Swarm.NodeID != ""
return &httpClient{ return &httpClient{
cli: cli, cli: cli,
@@ -95,7 +68,7 @@ func NewClient(cli DockerCLI, host Host) Client {
} }
// NewClientWithFilters creates a new instance of Client with docker filters // NewClientWithFilters creates a new instance of Client with docker filters
func NewLocalClient(hostname string) (Client, error) { func NewLocalClient(hostname string) (container.Client, error) {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil { if err != nil {
@@ -107,16 +80,8 @@ func NewLocalClient(hostname string) (Client, error) {
return nil, err return nil, err
} }
id := info.ID host := container.Host{
if info.Swarm.NodeID != "" {
id = info.Swarm.NodeID
}
host := Host{
ID: id,
Name: info.Name, Name: info.Name,
MemTotal: info.MemTotal,
NCPU: info.NCPU,
Endpoint: "local", Endpoint: "local",
Type: "local", Type: "local",
} }
@@ -128,7 +93,7 @@ func NewLocalClient(hostname string) (Client, error) {
return NewClient(cli, host), nil return NewClient(cli, host), nil
} }
func NewRemoteClient(host Host) (Client, error) { func NewRemoteClient(host container.Host) (container.Client, error) {
if host.URL.Scheme != "tcp" { if host.URL.Scheme != "tcp" {
return nil, fmt.Errorf("invalid scheme: %s", host.URL.Scheme) return nil, fmt.Errorf("invalid scheme: %s", host.URL.Scheme)
} }
@@ -158,33 +123,39 @@ func NewRemoteClient(host Host) (Client, error) {
} }
// Finds a container by id, skipping the filters // Finds a container by id, skipping the filters
func (d *httpClient) FindContainer(ctx context.Context, id string) (Container, error) { func (d *httpClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
log.Debug().Str("id", id).Msg("Finding container") log.Debug().Str("id", id).Msg("Finding container")
if json, err := d.cli.ContainerInspect(ctx, id); err == nil { if json, err := d.cli.ContainerInspect(ctx, id); err == nil {
return newContainerFromJSON(json, d.host.ID), nil return newContainerFromJSON(json, d.host.ID), nil
} else { } else {
return Container{}, err return container.Container{}, err
} }
} }
func (d *httpClient) ContainerActions(ctx context.Context, action ContainerAction, containerID string) error { func (d *httpClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error {
switch action { switch action {
case Start: case container.Start:
return d.cli.ContainerStart(ctx, containerID, container.StartOptions{}) return d.cli.ContainerStart(ctx, containerID, docker.StartOptions{})
case Stop: case container.Stop:
return d.cli.ContainerStop(ctx, containerID, container.StopOptions{}) return d.cli.ContainerStop(ctx, containerID, docker.StopOptions{})
case Restart: case container.Restart:
return d.cli.ContainerRestart(ctx, containerID, container.StopOptions{}) return d.cli.ContainerRestart(ctx, containerID, docker.StopOptions{})
default: default:
return fmt.Errorf("unknown action: %s", action) return fmt.Errorf("unknown action: %s", action)
} }
} }
func (d *httpClient) ListContainers(ctx context.Context, filter ContainerFilter) ([]Container, error) { func (d *httpClient) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
log.Debug().Interface("filter", filter).Str("host", d.host.Name).Msg("Listing containers") log.Debug().Interface("filter", filter).Str("host", d.host.Name).Msg("Listing containers")
containerListOptions := container.ListOptions{ filterArgs := filters.NewArgs()
Filters: filter.asArgs(), for key, values := range filter {
for _, value := range values {
filterArgs.Add(key, value)
}
}
containerListOptions := docker.ListOptions{
Filters: filterArgs,
All: true, All: true,
} }
list, err := d.cli.ContainerList(ctx, containerListOptions) list, err := d.cli.ContainerList(ctx, containerListOptions)
@@ -192,7 +163,7 @@ func (d *httpClient) ListContainers(ctx context.Context, filter ContainerFilter)
return nil, err return nil, err
} }
var containers = make([]Container, 0, len(list)) var containers = make([]container.Container, 0, len(list))
for _, c := range list { for _, c := range list {
containers = append(containers, newContainer(c, d.host.ID)) containers = append(containers, newContainer(c, d.host.ID))
} }
@@ -204,7 +175,7 @@ func (d *httpClient) ListContainers(ctx context.Context, filter ContainerFilter)
return containers, nil return containers, nil
} }
func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<- ContainerStat) error { func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<- container.ContainerStat) error {
response, err := d.cli.ContainerStats(ctx, id, true) response, err := d.cli.ContainerStats(ctx, id, true)
if err != nil { if err != nil {
@@ -213,7 +184,7 @@ func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<-
defer response.Body.Close() defer response.Body.Close()
decoder := json.NewDecoder(response.Body) decoder := json.NewDecoder(response.Body)
var v *container.StatsResponse var v *docker.StatsResponse
for { for {
if err := decoder.Decode(&v); err != nil { if err := decoder.Decode(&v); err != nil {
return err return err
@@ -243,7 +214,7 @@ func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<-
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil return nil
case stats <- ContainerStat{ case stats <- container.ContainerStat{
ID: id, ID: id,
CPUPercent: cpuPercent, CPUPercent: cpuPercent,
MemoryPercent: memPercent, MemoryPercent: memPercent,
@@ -254,13 +225,13 @@ func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<-
} }
} }
func (d *httpClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType StdType) (io.ReadCloser, error) { func (d *httpClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType container.StdType) (io.ReadCloser, error) {
log.Debug().Str("id", id).Time("since", since).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Streaming logs for container") log.Debug().Str("id", id).Time("since", since).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Streaming logs for container")
sinceQuery := since.Add(-50 * time.Millisecond).Format(time.RFC3339Nano) sinceQuery := since.Add(-50 * time.Millisecond).Format(time.RFC3339Nano)
options := container.LogsOptions{ options := docker.LogsOptions{
ShowStdout: stdType&STDOUT != 0, ShowStdout: stdType&container.STDOUT != 0,
ShowStderr: stdType&STDERR != 0, ShowStderr: stdType&container.STDERR != 0,
Follow: true, Follow: true,
Tail: strconv.Itoa(100), Tail: strconv.Itoa(100),
Timestamps: true, Timestamps: true,
@@ -275,7 +246,7 @@ func (d *httpClient) ContainerLogs(ctx context.Context, id string, since time.Ti
return reader, nil return reader, nil
} }
func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- ContainerEvent) error { func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- container.ContainerEvent) error {
dockerMessages, err := d.cli.Events(ctx, events.ListOptions{}) dockerMessages, err := d.cli.Events(ctx, events.ListOptions{})
for { for {
@@ -287,7 +258,7 @@ func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- Contai
case message := <-dockerMessages: case message := <-dockerMessages:
if message.Type == events.ContainerEventType && len(message.Actor.ID) > 0 { if message.Type == events.ContainerEventType && len(message.Actor.ID) > 0 {
messages <- ContainerEvent{ messages <- container.ContainerEvent{
ActorID: message.Actor.ID[:12], ActorID: message.Actor.ID[:12],
Name: string(message.Action), Name: string(message.Action),
Host: d.host.ID, Host: d.host.ID,
@@ -299,11 +270,11 @@ func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- Contai
} }
} }
func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType StdType) (io.ReadCloser, error) { func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType container.StdType) (io.ReadCloser, error) {
log.Debug().Str("id", id).Time("from", from).Time("to", to).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Fetching logs between dates for container") log.Debug().Str("id", id).Time("from", from).Time("to", to).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Fetching logs between dates for container")
options := container.LogsOptions{ options := docker.LogsOptions{
ShowStdout: stdType&STDOUT != 0, ShowStdout: stdType&container.STDOUT != 0,
ShowStderr: stdType&STDERR != 0, ShowStderr: stdType&container.STDERR != 0,
Timestamps: true, Timestamps: true,
Since: from.Format(time.RFC3339Nano), Since: from.Format(time.RFC3339Nano),
Until: to.Format(time.RFC3339Nano), Until: to.Format(time.RFC3339Nano),
@@ -317,11 +288,12 @@ func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, f
return reader, nil return reader, nil
} }
func (d *httpClient) Ping(ctx context.Context) (types.Ping, error) { func (d *httpClient) Ping(ctx context.Context) error {
return d.cli.Ping(ctx) _, err := d.cli.Ping(ctx)
return err
} }
func (d *httpClient) Host() Host { func (d *httpClient) Host() container.Host {
log.Debug().Str("host", d.host.Name).Msg("Fetching host") log.Debug().Str("host", d.host.Name).Msg("Fetching host")
return d.host return d.host
} }
@@ -330,11 +302,7 @@ func (d *httpClient) IsSwarmMode() bool {
return d.info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive return d.info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive
} }
func (d *httpClient) SystemInfo() system.Info { func newContainer(c types.Container, host string) container.Container {
return d.info
}
func newContainer(c types.Container, host string) Container {
name := "no name" name := "no name"
if c.Labels["dev.dozzle.name"] != "" { if c.Labels["dev.dozzle.name"] != "" {
name = c.Labels["dev.dozzle.name"] name = c.Labels["dev.dozzle.name"]
@@ -346,7 +314,7 @@ func newContainer(c types.Container, host string) Container {
if c.Labels["dev.dozzle.group"] != "" { if c.Labels["dev.dozzle.group"] != "" {
group = c.Labels["dev.dozzle.group"] group = c.Labels["dev.dozzle.group"]
} }
return Container{ return container.Container{
ID: c.ID[:12], ID: c.ID[:12],
Name: name, Name: name,
Image: c.Image, Image: c.Image,
@@ -355,12 +323,12 @@ func newContainer(c types.Container, host string) Container {
State: c.State, State: c.State,
Host: host, Host: host,
Labels: c.Labels, Labels: c.Labels,
Stats: utils.NewRingBuffer[ContainerStat](300), // 300 seconds of stats Stats: utils.NewRingBuffer[container.ContainerStat](300), // 300 seconds of stats
Group: group, Group: group,
} }
} }
func newContainerFromJSON(c types.ContainerJSON, host string) Container { func newContainerFromJSON(c types.ContainerJSON, host string) container.Container {
name := "no name" name := "no name"
if c.Config.Labels["dev.dozzle.name"] != "" { if c.Config.Labels["dev.dozzle.name"] != "" {
name = c.Config.Labels["dev.dozzle.name"] name = c.Config.Labels["dev.dozzle.name"]
@@ -373,7 +341,7 @@ func newContainerFromJSON(c types.ContainerJSON, host string) Container {
group = c.Config.Labels["dev.dozzle.group"] group = c.Config.Labels["dev.dozzle.group"]
} }
container := Container{ container := container.Container{
ID: c.ID[:12], ID: c.ID[:12],
Name: name, Name: name,
Image: c.Config.Image, Image: c.Config.Image,
@@ -381,7 +349,7 @@ func newContainerFromJSON(c types.ContainerJSON, host string) Container {
State: c.State.Status, State: c.State.Status,
Host: host, Host: host,
Labels: c.Config.Labels, Labels: c.Config.Labels,
Stats: utils.NewRingBuffer[ContainerStat](300), // 300 seconds of stats Stats: utils.NewRingBuffer[container.ContainerStat](300), // 300 seconds of stats
Group: group, Group: group,
Tty: c.Config.Tty, Tty: c.Config.Tty,
} }

View File

@@ -10,8 +10,9 @@ import (
"testing" "testing"
"github.com/amir20/dozzle/internal/container"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" docker "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/system" "github.com/docker/docker/api/types/system"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
@@ -23,7 +24,7 @@ type mockedProxy struct {
DockerCLI DockerCLI
} }
func (m *mockedProxy) ContainerList(context.Context, container.ListOptions) ([]types.Container, error) { func (m *mockedProxy) ContainerList(context.Context, docker.ListOptions) ([]types.Container, error) {
args := m.Called() args := m.Called()
containers, ok := args.Get(0).([]types.Container) containers, ok := args.Get(0).([]types.Container)
if !ok && args.Get(0) != nil { if !ok && args.Get(0) != nil {
@@ -33,7 +34,7 @@ func (m *mockedProxy) ContainerList(context.Context, container.ListOptions) ([]t
} }
func (m *mockedProxy) ContainerLogs(ctx context.Context, id string, options container.LogsOptions) (io.ReadCloser, error) { func (m *mockedProxy) ContainerLogs(ctx context.Context, id string, options docker.LogsOptions) (io.ReadCloser, error) {
args := m.Called(ctx, id, options) args := m.Called(ctx, id, options)
reader, ok := args.Get(0).(io.ReadCloser) reader, ok := args.Get(0).(io.ReadCloser)
if !ok && args.Get(0) != nil { if !ok && args.Get(0) != nil {
@@ -47,11 +48,11 @@ func (m *mockedProxy) ContainerInspect(ctx context.Context, containerID string)
return args.Get(0).(types.ContainerJSON), args.Error(1) return args.Get(0).(types.ContainerJSON), args.Error(1)
} }
func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, stream bool) (container.StatsResponseReader, error) { func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, stream bool) (docker.StatsResponseReader, error) {
return container.StatsResponseReader{}, nil return docker.StatsResponseReader{}, nil
} }
func (m *mockedProxy) ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error { func (m *mockedProxy) ContainerStart(ctx context.Context, containerID string, options docker.StartOptions) error {
args := m.Called(ctx, containerID, options) args := m.Called(ctx, containerID, options)
err := args.Get(0) err := args.Get(0)
@@ -63,7 +64,7 @@ func (m *mockedProxy) ContainerStart(ctx context.Context, containerID string, op
return nil return nil
} }
func (m *mockedProxy) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error { func (m *mockedProxy) ContainerStop(ctx context.Context, containerID string, options docker.StopOptions) error {
args := m.Called(ctx, containerID, options) args := m.Called(ctx, containerID, options)
err := args.Get(0) err := args.Get(0)
@@ -74,7 +75,7 @@ func (m *mockedProxy) ContainerStop(ctx context.Context, containerID string, opt
return nil return nil
} }
func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error { func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string, options docker.StopOptions) error {
args := m.Called(ctx, containerID, options) args := m.Called(ctx, containerID, options)
err := args.Get(0) err := args.Get(0)
@@ -89,9 +90,9 @@ func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string,
func Test_dockerClient_ListContainers_null(t *testing.T) { func Test_dockerClient_ListContainers_null(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}} client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
list, err := client.ListContainers(context.Background(), ContainerFilter{}) list, err := client.ListContainers(context.Background(), container.ContainerFilter{})
assert.Empty(t, list, "list should be empty") assert.Empty(t, list, "list should be empty")
require.NoError(t, err, "error should not return an error.") require.NoError(t, err, "error should not return an error.")
@@ -101,9 +102,9 @@ func Test_dockerClient_ListContainers_null(t *testing.T) {
func Test_dockerClient_ListContainers_error(t *testing.T) { func Test_dockerClient_ListContainers_error(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test")) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}} client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
list, err := client.ListContainers(context.Background(), ContainerFilter{}) list, err := client.ListContainers(context.Background(), container.ContainerFilter{})
assert.Nil(t, list, "list should be nil") assert.Nil(t, list, "list should be nil")
require.Error(t, err, "test.") require.Error(t, err, "test.")
@@ -124,9 +125,9 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}} client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
list, err := client.ListContainers(context.Background(), ContainerFilter{}) list, err := client.ListContainers(context.Background(), container.ContainerFilter{})
require.NoError(t, err, "error should not return an error.") require.NoError(t, err, "error should not return an error.")
Ids := []string{"1234567890_a", "abcdefghijkl"} Ids := []string{"1234567890_a", "abcdefghijkl"}
@@ -149,7 +150,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
reader := io.NopCloser(bytes.NewReader(b)) reader := io.NopCloser(bytes.NewReader(b))
since := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC) since := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
options := container.LogsOptions{ options := docker.LogsOptions{
ShowStdout: true, ShowStdout: true,
ShowStderr: true, ShowStderr: true,
Follow: true, Follow: true,
@@ -158,8 +159,8 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
Since: "2020-12-31T23:59:59.95Z"} Since: "2020-12-31T23:59:59.95Z"}
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil) proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}} client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
logReader, _ := client.ContainerLogs(context.Background(), id, since, STDALL) logReader, _ := client.ContainerLogs(context.Background(), id, since, container.STDALL)
actual, _ := io.ReadAll(logReader) actual, _ := io.ReadAll(logReader)
assert.Equal(t, string(b), string(actual), "message doesn't match expected") assert.Equal(t, string(b), string(actual), "message doesn't match expected")
@@ -172,9 +173,9 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test")) proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}} client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
reader, err := client.ContainerLogs(context.Background(), id, time.Time{}, STDALL) reader, err := client.ContainerLogs(context.Background(), id, time.Time{}, container.STDALL)
assert.Nil(t, reader, "reader should be nil") assert.Nil(t, reader, "reader should be nil")
assert.Error(t, err, "error should have been returned") assert.Error(t, err, "error should have been returned")
@@ -185,10 +186,10 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
state := &types.ContainerState{Status: "running", StartedAt: time.Now().Format(time.RFC3339Nano)} state := &types.ContainerState{Status: "running", StartedAt: time.Now().Format(time.RFC3339Nano)}
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijklmnopqrst", State: state}, Config: &container.Config{Tty: false}} json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijklmnopqrst", State: state}, Config: &docker.Config{Tty: false}}
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil) proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}} client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
container, err := client.FindContainer(context.Background(), "abcdefghijkl") container, err := client.FindContainer(context.Background(), "abcdefghijkl")
require.NoError(t, err, "error should not be thrown") require.NoError(t, err, "error should not be thrown")
@@ -201,7 +202,7 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
func Test_dockerClient_FindContainer_error(t *testing.T) { func Test_dockerClient_FindContainer_error(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerInspect", mock.Anything, "not_valid").Return(types.ContainerJSON{}, errors.New("not found")) proxy.On("ContainerInspect", mock.Anything, "not_valid").Return(types.ContainerJSON{}, errors.New("not found"))
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}} client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
_, err := client.FindContainer(context.Background(), "not_valid") _, err := client.FindContainer(context.Background(), "not_valid")
require.Error(t, err, "error should be thrown") require.Error(t, err, "error should be thrown")
@@ -211,24 +212,24 @@ func Test_dockerClient_FindContainer_error(t *testing.T) {
func Test_dockerClient_ContainerActions_happy(t *testing.T) { func Test_dockerClient_ContainerActions_happy(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}} client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
state := &types.ContainerState{Status: "running", StartedAt: time.Now().Format(time.RFC3339Nano)} state := &types.ContainerState{Status: "running", StartedAt: time.Now().Format(time.RFC3339Nano)}
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijkl", State: state}, Config: &container.Config{Tty: false}} json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ID: "abcdefghijkl", State: state}, Config: &docker.Config{Tty: false}}
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil) proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
proxy.On("ContainerStart", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil) proxy.On("ContainerStart", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil)
proxy.On("ContainerStop", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil) proxy.On("ContainerStop", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil)
proxy.On("ContainerRestart", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil) proxy.On("ContainerRestart", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil)
container, err := client.FindContainer(context.Background(), "abcdefghijkl") c, err := client.FindContainer(context.Background(), "abcdefghijkl")
require.NoError(t, err, "error should not be thrown") require.NoError(t, err, "error should not be thrown")
assert.Equal(t, container.ID, "abcdefghijkl") assert.Equal(t, c.ID, "abcdefghijkl")
actions := []string{"start", "stop", "restart"} actions := []string{"start", "stop", "restart"}
for _, action := range actions { for _, action := range actions {
err := client.ContainerActions(context.Background(), ContainerAction(action), container.ID) err := client.ContainerActions(context.Background(), container.ContainerAction(action), c.ID)
require.NoError(t, err, "error should not be thrown") require.NoError(t, err, "error should not be thrown")
assert.Equal(t, err, nil) assert.Equal(t, err, nil)
} }
@@ -239,18 +240,18 @@ func Test_dockerClient_ContainerActions_happy(t *testing.T) {
func Test_dockerClient_ContainerActions_error(t *testing.T) { func Test_dockerClient_ContainerActions_error(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}} client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
proxy.On("ContainerInspect", mock.Anything, "random-id").Return(types.ContainerJSON{}, errors.New("not found")) proxy.On("ContainerInspect", mock.Anything, "random-id").Return(types.ContainerJSON{}, errors.New("not found"))
proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test")) proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
proxy.On("ContainerStop", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test")) proxy.On("ContainerStop", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
proxy.On("ContainerRestart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test")) proxy.On("ContainerRestart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
container, err := client.FindContainer(context.Background(), "random-id") c, err := client.FindContainer(context.Background(), "random-id")
require.Error(t, err, "error should be thrown") require.Error(t, err, "error should be thrown")
actions := []string{"start", "stop", "restart"} actions := []string{"start", "stop", "restart"}
for _, action := range actions { for _, action := range actions {
err := client.ContainerActions(context.Background(), ContainerAction(action), container.ID) err := client.ContainerActions(context.Background(), container.ContainerAction(action), c.ID)
require.Error(t, err, "error should be thrown") require.Error(t, err, "error should be thrown")
assert.Error(t, err, "error should have been returned") assert.Error(t, err, "error should have been returned")
} }

View File

@@ -5,7 +5,7 @@ import (
"crypto/tls" "crypto/tls"
"github.com/amir20/dozzle/internal/agent" "github.com/amir20/dozzle/internal/agent"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -14,7 +14,7 @@ func RPCRequest(ctx context.Context, addr string, certs tls.Certificate) error {
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to create agent client") log.Fatal().Err(err).Msg("Failed to create agent client")
} }
containers, err := client.ListContainers(ctx, docker.ContainerFilter{}) containers, err := client.ListContainers(ctx, container.ContainerFilter{})
log.Trace().Int("containers", len(containers)).Msg("Healtcheck RPC request completed") log.Trace().Int("containers", len(containers)).Msg("Healtcheck RPC request completed")
return err return err
} }

View File

@@ -2,12 +2,12 @@ package cli
import ( import (
"github.com/amir20/dozzle/internal/analytics" "github.com/amir20/dozzle/internal/analytics"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/types" "github.com/amir20/dozzle/types"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
func StartEvent(args Args, mode string, client docker.Client, subCommand string) { func StartEvent(args Args, mode string, client container.Client, subCommand string) {
if args.NoAnalytics { if args.NoAnalytics {
return return
} }
@@ -29,7 +29,7 @@ func StartEvent(args Args, mode string, client docker.Client, subCommand string)
host := client.Host() host := client.Host()
event.ServerID = host.ID event.ServerID = host.ID
event.ServerVersion = host.DockerVersion event.ServerVersion = host.DockerVersion
event.IsSwarmMode = client.SystemInfo().Swarm.NodeID != "" event.IsSwarmMode = host.Swarm
} else { } else {
event.ServerID = "n/a" event.ServerID = "n/a"
} }

View File

@@ -4,19 +4,20 @@ import (
"context" "context"
"embed" "embed"
"github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/docker"
docker_support "github.com/amir20/dozzle/internal/support/docker" docker_support "github.com/amir20/dozzle/internal/support/docker"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
func CreateMultiHostService(embeddedCerts embed.FS, args Args) (docker.Client, *docker_support.MultiHostService) { func CreateMultiHostService(embeddedCerts embed.FS, args Args) (container.Client, *docker_support.MultiHostService) {
var clients []docker_support.ClientService var clients []docker_support.ClientService
if len(args.RemoteHost) > 0 { if len(args.RemoteHost) > 0 {
log.Info().Msg(`Consider using Dozzle's remote agent to manage remote hosts. See https://dozzle.dev/guide/agent for more information`) log.Info().Msg(`Consider using Dozzle's remote agent to manage remote hosts. See https://dozzle.dev/guide/agent for more information`)
} }
for _, remoteHost := range args.RemoteHost { for _, remoteHost := range args.RemoteHost {
host, err := docker.ParseConnection(remoteHost) host, err := container.ParseConnection(remoteHost)
if err != nil { if err != nil {
log.Fatal().Err(err).Interface("host", remoteHost).Msg("Could not parse remote host") log.Fatal().Err(err).Interface("host", remoteHost).Msg("Could not parse remote host")
} }

View File

@@ -7,13 +7,13 @@ import (
"time" "time"
"github.com/amir20/dozzle/internal/agent" "github.com/amir20/dozzle/internal/agent"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
type agentService struct { type agentService struct {
client *agent.Client client *agent.Client
host docker.Host host container.Host
} }
func NewAgentService(client *agent.Client) ClientService { func NewAgentService(client *agent.Client) ClientService {
@@ -22,28 +22,28 @@ func NewAgentService(client *agent.Client) ClientService {
} }
} }
func (a *agentService) FindContainer(ctx context.Context, id string, filter docker.ContainerFilter) (docker.Container, error) { func (a *agentService) FindContainer(ctx context.Context, id string, filter container.ContainerFilter) (container.Container, error) {
return a.client.FindContainer(ctx, id) return a.client.FindContainer(ctx, id)
} }
func (a *agentService) RawLogs(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (io.ReadCloser, error) { func (a *agentService) RawLogs(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error) {
return a.client.StreamRawBytes(ctx, container.ID, from, to, stdTypes) return a.client.StreamRawBytes(ctx, container.ID, from, to, stdTypes)
} }
func (a *agentService) LogsBetweenDates(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (<-chan *docker.LogEvent, error) { func (a *agentService) LogsBetweenDates(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
return a.client.LogsBetweenDates(ctx, container.ID, from, to, stdTypes) return a.client.LogsBetweenDates(ctx, container.ID, from, to, stdTypes)
} }
func (a *agentService) StreamLogs(ctx context.Context, container docker.Container, from time.Time, stdTypes docker.StdType, events chan<- *docker.LogEvent) error { func (a *agentService) StreamLogs(ctx context.Context, container container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
return a.client.StreamContainerLogs(ctx, container.ID, from, stdTypes, events) return a.client.StreamContainerLogs(ctx, container.ID, from, stdTypes, events)
} }
func (a *agentService) ListContainers(ctx context.Context, filter docker.ContainerFilter) ([]docker.Container, error) { func (a *agentService) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
log.Debug().Interface("filter", filter).Msg("Listing containers from agent") log.Debug().Interface("filter", filter).Msg("Listing containers from agent")
return a.client.ListContainers(ctx, filter) return a.client.ListContainers(ctx, filter)
} }
func (a *agentService) Host(ctx context.Context) (docker.Host, error) { func (a *agentService) Host(ctx context.Context) (container.Host, error) {
host, err := a.client.Host(ctx) host, err := a.client.Host(ctx)
if err != nil { if err != nil {
host := a.host host := a.host
@@ -55,18 +55,18 @@ func (a *agentService) Host(ctx context.Context) (docker.Host, error) {
return a.host, err return a.host, err
} }
func (a *agentService) SubscribeStats(ctx context.Context, stats chan<- docker.ContainerStat) { func (a *agentService) SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat) {
go a.client.StreamStats(ctx, stats) go a.client.StreamStats(ctx, stats)
} }
func (a *agentService) SubscribeEvents(ctx context.Context, events chan<- docker.ContainerEvent) { func (a *agentService) SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent) {
go a.client.StreamEvents(ctx, events) go a.client.StreamEvents(ctx, events)
} }
func (d *agentService) SubscribeContainersStarted(ctx context.Context, containers chan<- docker.Container) { func (d *agentService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container) {
go d.client.StreamNewContainers(ctx, containers) go d.client.StreamNewContainers(ctx, containers)
} }
func (a *agentService) ContainerAction(ctx context.Context, container docker.Container, action docker.ContainerAction) error { func (a *agentService) ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error {
return a.client.ContainerAction(ctx, container.ID, action) return a.client.ContainerAction(ctx, container.ID, action)
} }

View File

@@ -5,59 +5,59 @@ import (
"io" "io"
"time" "time"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
) )
type ClientService interface { type ClientService interface {
FindContainer(ctx context.Context, id string, filter docker.ContainerFilter) (docker.Container, error) FindContainer(ctx context.Context, id string, filter container.ContainerFilter) (container.Container, error)
ListContainers(ctx context.Context, filter docker.ContainerFilter) ([]docker.Container, error) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error)
Host(ctx context.Context) (docker.Host, error) Host(ctx context.Context) (container.Host, error)
ContainerAction(ctx context.Context, container docker.Container, action docker.ContainerAction) error ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error
LogsBetweenDates(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (<-chan *docker.LogEvent, error) LogsBetweenDates(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error)
RawLogs(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (io.ReadCloser, error) RawLogs(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error)
// Subscriptions // Subscriptions
SubscribeStats(ctx context.Context, stats chan<- docker.ContainerStat) SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat)
SubscribeEvents(ctx context.Context, events chan<- docker.ContainerEvent) SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent)
SubscribeContainersStarted(ctx context.Context, containers chan<- docker.Container) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container)
// Blocking streaming functions that should be used in a goroutine // Blocking streaming functions that should be used in a goroutine
StreamLogs(ctx context.Context, container docker.Container, from time.Time, stdTypes docker.StdType, events chan<- *docker.LogEvent) error StreamLogs(ctx context.Context, container container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error
} }
type dockerClientService struct { type dockerClientService struct {
client docker.Client client container.Client
store *docker.ContainerStore store *container.ContainerStore
} }
func NewDockerClientService(client docker.Client, filter docker.ContainerFilter) ClientService { func NewDockerClientService(client container.Client, filter container.ContainerFilter) ClientService {
return &dockerClientService{ return &dockerClientService{
client: client, client: client,
store: docker.NewContainerStore(context.Background(), client, filter), store: container.NewContainerStore(context.Background(), client, filter),
} }
} }
func (d *dockerClientService) RawLogs(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (io.ReadCloser, error) { func (d *dockerClientService) RawLogs(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error) {
return d.client.ContainerLogsBetweenDates(ctx, container.ID, from, to, stdTypes) return d.client.ContainerLogsBetweenDates(ctx, container.ID, from, to, stdTypes)
} }
func (d *dockerClientService) LogsBetweenDates(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (<-chan *docker.LogEvent, error) { func (d *dockerClientService) LogsBetweenDates(ctx context.Context, c container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
reader, err := d.client.ContainerLogsBetweenDates(ctx, container.ID, from, to, stdTypes) reader, err := d.client.ContainerLogsBetweenDates(ctx, c.ID, from, to, stdTypes)
if err != nil { if err != nil {
return nil, err return nil, err
} }
g := docker.NewEventGenerator(ctx, reader, container) g := container.NewEventGenerator(ctx, reader, c)
return g.Events, nil return g.Events, nil
} }
func (d *dockerClientService) StreamLogs(ctx context.Context, container docker.Container, from time.Time, stdTypes docker.StdType, events chan<- *docker.LogEvent) error { func (d *dockerClientService) StreamLogs(ctx context.Context, c container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
reader, err := d.client.ContainerLogs(ctx, container.ID, from, stdTypes) reader, err := d.client.ContainerLogs(ctx, c.ID, from, stdTypes)
if err != nil { if err != nil {
return err return err
} }
g := docker.NewEventGenerator(ctx, reader, container) g := container.NewEventGenerator(ctx, reader, c)
for event := range g.Events { for event := range g.Events {
events <- event events <- event
} }
@@ -70,30 +70,30 @@ func (d *dockerClientService) StreamLogs(ctx context.Context, container docker.C
} }
} }
func (d *dockerClientService) FindContainer(ctx context.Context, id string, filter docker.ContainerFilter) (docker.Container, error) { func (d *dockerClientService) FindContainer(ctx context.Context, id string, filter container.ContainerFilter) (container.Container, error) {
return d.store.FindContainer(id, filter) return d.store.FindContainer(id, filter)
} }
func (d *dockerClientService) ContainerAction(ctx context.Context, container docker.Container, action docker.ContainerAction) error { func (d *dockerClientService) ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error {
return d.client.ContainerActions(ctx, action, container.ID) return d.client.ContainerActions(ctx, action, container.ID)
} }
func (d *dockerClientService) ListContainers(ctx context.Context, filter docker.ContainerFilter) ([]docker.Container, error) { func (d *dockerClientService) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
return d.store.ListContainers(filter) return d.store.ListContainers(filter)
} }
func (d *dockerClientService) Host(ctx context.Context) (docker.Host, error) { func (d *dockerClientService) Host(ctx context.Context) (container.Host, error) {
return d.client.Host(), nil return d.client.Host(), nil
} }
func (d *dockerClientService) SubscribeStats(ctx context.Context, stats chan<- docker.ContainerStat) { func (d *dockerClientService) SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat) {
d.store.SubscribeStats(ctx, stats) d.store.SubscribeStats(ctx, stats)
} }
func (d *dockerClientService) SubscribeEvents(ctx context.Context, events chan<- docker.ContainerEvent) { func (d *dockerClientService) SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent) {
d.store.SubscribeEvents(ctx, events) d.store.SubscribeEvents(ctx, events)
} }
func (d *dockerClientService) SubscribeContainersStarted(ctx context.Context, containers chan<- docker.Container) { func (d *dockerClientService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container) {
d.store.SubscribeNewContainers(ctx, containers) d.store.SubscribeNewContainers(ctx, containers)
} }

View File

@@ -5,26 +5,26 @@ import (
"io" "io"
"time" "time"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
) )
type containerService struct { type containerService struct {
clientService ClientService clientService ClientService
Container docker.Container Container container.Container
} }
func (c *containerService) RawLogs(ctx context.Context, from time.Time, to time.Time, stdTypes docker.StdType) (io.ReadCloser, error) { func (c *containerService) RawLogs(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error) {
return c.clientService.RawLogs(ctx, c.Container, from, to, stdTypes) return c.clientService.RawLogs(ctx, c.Container, from, to, stdTypes)
} }
func (c *containerService) LogsBetweenDates(ctx context.Context, from time.Time, to time.Time, stdTypes docker.StdType) (<-chan *docker.LogEvent, error) { func (c *containerService) LogsBetweenDates(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
return c.clientService.LogsBetweenDates(ctx, c.Container, from, to, stdTypes) return c.clientService.LogsBetweenDates(ctx, c.Container, from, to, stdTypes)
} }
func (c *containerService) StreamLogs(ctx context.Context, from time.Time, stdTypes docker.StdType, events chan<- *docker.LogEvent) error { func (c *containerService) StreamLogs(ctx context.Context, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
return c.clientService.StreamLogs(ctx, c.Container, from, stdTypes, events) return c.clientService.StreamLogs(ctx, c.Container, from, stdTypes, events)
} }
func (c *containerService) Action(ctx context.Context, action docker.ContainerAction) error { func (c *containerService) Action(ctx context.Context, action container.ContainerAction) error {
return c.clientService.ContainerAction(ctx, c.Container, action) return c.clientService.ContainerAction(ctx, c.Container, action)
} }

View File

@@ -5,14 +5,14 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
type ContainerFilter = func(*docker.Container) bool type ContainerFilter = func(*container.Container) bool
type HostUnavailableError struct { type HostUnavailableError struct {
Host docker.Host Host container.Host
Err error Err error
} }
@@ -24,9 +24,9 @@ type ClientManager interface {
Find(id string) (ClientService, bool) Find(id string) (ClientService, bool)
List() []ClientService List() []ClientService
RetryAndList() ([]ClientService, []error) RetryAndList() ([]ClientService, []error)
Subscribe(ctx context.Context, channel chan<- docker.Host) Subscribe(ctx context.Context, channel chan<- container.Host)
Hosts(ctx context.Context) []docker.Host Hosts(ctx context.Context) []container.Host
LocalClients() []docker.Client LocalClients() []container.Client
} }
type MultiHostService struct { type MultiHostService struct {
@@ -43,7 +43,7 @@ func NewMultiHostService(manager ClientManager, timeout time.Duration) *MultiHos
return m return m
} }
func (m *MultiHostService) FindContainer(host string, id string, filter docker.ContainerFilter) (*containerService, error) { func (m *MultiHostService) FindContainer(host string, id string, filter container.ContainerFilter) (*containerService, error) {
client, ok := m.manager.Find(host) client, ok := m.manager.Find(host)
if !ok { if !ok {
return nil, fmt.Errorf("host %s not found", host) return nil, fmt.Errorf("host %s not found", host)
@@ -61,7 +61,7 @@ func (m *MultiHostService) FindContainer(host string, id string, filter docker.C
}, nil }, nil
} }
func (m *MultiHostService) ListContainersForHost(host string, filter docker.ContainerFilter) ([]docker.Container, error) { func (m *MultiHostService) ListContainersForHost(host string, filter container.ContainerFilter) ([]container.Container, error) {
client, ok := m.manager.Find(host) client, ok := m.manager.Find(host)
if !ok { if !ok {
return nil, fmt.Errorf("host %s not found", host) return nil, fmt.Errorf("host %s not found", host)
@@ -72,8 +72,8 @@ func (m *MultiHostService) ListContainersForHost(host string, filter docker.Cont
return client.ListContainers(ctx, filter) return client.ListContainers(ctx, filter)
} }
func (m *MultiHostService) ListAllContainers(filter docker.ContainerFilter) ([]docker.Container, []error) { func (m *MultiHostService) ListAllContainers(filter container.ContainerFilter) ([]container.Container, []error) {
containers := make([]docker.Container, 0) containers := make([]container.Container, 0)
clients, errors := m.manager.RetryAndList() clients, errors := m.manager.RetryAndList()
for _, client := range clients { for _, client := range clients {
@@ -94,9 +94,9 @@ func (m *MultiHostService) ListAllContainers(filter docker.ContainerFilter) ([]d
return containers, errors return containers, errors
} }
func (m *MultiHostService) ListAllContainersFiltered(userFilter docker.ContainerFilter, filter ContainerFilter) ([]docker.Container, []error) { func (m *MultiHostService) ListAllContainersFiltered(userFilter container.ContainerFilter, filter ContainerFilter) ([]container.Container, []error) {
containers, err := m.ListAllContainers(userFilter) containers, err := m.ListAllContainers(userFilter)
filtered := make([]docker.Container, 0, len(containers)) filtered := make([]container.Container, 0, len(containers))
for _, container := range containers { for _, container := range containers {
if filter(&container) { if filter(&container) {
filtered = append(filtered, container) filtered = append(filtered, container)
@@ -105,15 +105,15 @@ func (m *MultiHostService) ListAllContainersFiltered(userFilter docker.Container
return filtered, err return filtered, err
} }
func (m *MultiHostService) SubscribeEventsAndStats(ctx context.Context, events chan<- docker.ContainerEvent, stats chan<- docker.ContainerStat) { func (m *MultiHostService) SubscribeEventsAndStats(ctx context.Context, events chan<- container.ContainerEvent, stats chan<- container.ContainerStat) {
for _, client := range m.manager.List() { for _, client := range m.manager.List() {
client.SubscribeEvents(ctx, events) client.SubscribeEvents(ctx, events)
client.SubscribeStats(ctx, stats) client.SubscribeStats(ctx, stats)
} }
} }
func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, containers chan<- docker.Container, filter ContainerFilter) { func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container, filter ContainerFilter) {
newContainers := make(chan docker.Container) newContainers := make(chan container.Container)
for _, client := range m.manager.List() { for _, client := range m.manager.List() {
client.SubscribeContainersStarted(ctx, newContainers) client.SubscribeContainersStarted(ctx, newContainers)
} }
@@ -139,25 +139,25 @@ func (m *MultiHostService) TotalClients() int {
return len(m.manager.List()) return len(m.manager.List())
} }
func (m *MultiHostService) Hosts() []docker.Host { func (m *MultiHostService) Hosts() []container.Host {
ctx, cancel := context.WithTimeout(context.Background(), m.timeout) ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
defer cancel() defer cancel()
return m.manager.Hosts(ctx) return m.manager.Hosts(ctx)
} }
func (m *MultiHostService) LocalHost() (docker.Host, error) { func (m *MultiHostService) LocalHost() (container.Host, error) {
for _, host := range m.Hosts() { for _, host := range m.Hosts() {
if host.Type == "local" { if host.Type == "local" {
return host, nil return host, nil
} }
} }
return docker.Host{}, fmt.Errorf("local host not found") return container.Host{}, fmt.Errorf("local host not found")
} }
func (m *MultiHostService) SubscribeAvailableHosts(ctx context.Context, hosts chan<- docker.Host) { func (m *MultiHostService) SubscribeAvailableHosts(ctx context.Context, hosts chan<- container.Host) {
m.manager.Subscribe(ctx, hosts) m.manager.Subscribe(ctx, hosts)
} }
func (m *MultiHostService) LocalClients() []docker.Client { func (m *MultiHostService) LocalClients() []container.Client {
return m.manager.LocalClients() return m.manager.LocalClients()
} }

View File

@@ -8,7 +8,7 @@ import (
"time" "time"
"github.com/amir20/dozzle/internal/agent" "github.com/amir20/dozzle/internal/agent"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
"github.com/samber/lo" "github.com/samber/lo"
lop "github.com/samber/lo/parallel" lop "github.com/samber/lo/parallel"
@@ -21,7 +21,7 @@ type RetriableClientManager struct {
failedAgents []string failedAgents []string
certs tls.Certificate certs tls.Certificate
mu sync.RWMutex mu sync.RWMutex
subscribers *xsync.MapOf[context.Context, chan<- docker.Host] subscribers *xsync.MapOf[context.Context, chan<- container.Host]
timeout time.Duration timeout time.Duration
} }
@@ -72,12 +72,12 @@ func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls
clients: clientMap, clients: clientMap,
failedAgents: failed, failedAgents: failed,
certs: certs, certs: certs,
subscribers: xsync.NewMapOf[context.Context, chan<- docker.Host](), subscribers: xsync.NewMapOf[context.Context, chan<- container.Host](),
timeout: timeout, timeout: timeout,
} }
} }
func (m *RetriableClientManager) Subscribe(ctx context.Context, channel chan<- docker.Host) { func (m *RetriableClientManager) Subscribe(ctx context.Context, channel chan<- container.Host) {
m.subscribers.Store(ctx, channel) m.subscribers.Store(ctx, channel)
go func() { go func() {
@@ -111,11 +111,11 @@ func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) {
} }
m.clients[host.ID] = NewAgentService(agent) m.clients[host.ID] = NewAgentService(agent)
m.subscribers.Range(func(ctx context.Context, channel chan<- docker.Host) bool { m.subscribers.Range(func(ctx context.Context, channel chan<- container.Host) bool {
host.Available = true host.Available = true
// We don't want to block the subscribers in event.go // We don't want to block the subscribers in event.go
go func(host docker.Host) { go func(host container.Host) {
select { select {
case channel <- host: case channel <- host:
case <-ctx.Done(): case <-ctx.Done():
@@ -152,10 +152,10 @@ func (m *RetriableClientManager) String() string {
return fmt.Sprintf("RetriableClientManager{clients: %d, failedAgents: %d}", len(m.clients), len(m.failedAgents)) return fmt.Sprintf("RetriableClientManager{clients: %d, failedAgents: %d}", len(m.clients), len(m.failedAgents))
} }
func (m *RetriableClientManager) Hosts(ctx context.Context) []docker.Host { func (m *RetriableClientManager) Hosts(ctx context.Context) []container.Host {
clients := m.List() clients := m.List()
hosts := lop.Map(clients, func(client ClientService, _ int) docker.Host { hosts := lop.Map(clients, func(client ClientService, _ int) container.Host {
host, err := client.Host(ctx) host, err := client.Host(ctx)
if err != nil { if err != nil {
log.Warn().Err(err).Str("host", host.Name).Msg("error fetching host info for client") log.Warn().Err(err).Str("host", host.Name).Msg("error fetching host info for client")
@@ -168,7 +168,7 @@ func (m *RetriableClientManager) Hosts(ctx context.Context) []docker.Host {
}) })
for _, endpoint := range m.failedAgents { for _, endpoint := range m.failedAgents {
hosts = append(hosts, docker.Host{ hosts = append(hosts, container.Host{
ID: endpoint, ID: endpoint,
Name: endpoint, Name: endpoint,
Endpoint: endpoint, Endpoint: endpoint,
@@ -180,10 +180,10 @@ func (m *RetriableClientManager) Hosts(ctx context.Context) []docker.Host {
return hosts return hosts
} }
func (m *RetriableClientManager) LocalClients() []docker.Client { func (m *RetriableClientManager) LocalClients() []container.Client {
services := m.List() services := m.List()
clients := make([]docker.Client, 0) clients := make([]container.Client, 0)
for _, service := range services { for _, service := range services {
if clientService, ok := service.(*dockerClientService); ok { if clientService, ok := service.(*dockerClientService); ok {

View File

@@ -10,7 +10,7 @@ import (
"time" "time"
"github.com/amir20/dozzle/internal/agent" "github.com/amir20/dozzle/internal/agent"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
"github.com/samber/lo" "github.com/samber/lo"
lop "github.com/samber/lo/parallel" lop "github.com/samber/lo/parallel"
@@ -22,8 +22,8 @@ type SwarmClientManager struct {
clients map[string]ClientService clients map[string]ClientService
certs tls.Certificate certs tls.Certificate
mu sync.RWMutex mu sync.RWMutex
subscribers *xsync.MapOf[context.Context, chan<- docker.Host] subscribers *xsync.MapOf[context.Context, chan<- container.Host]
localClient docker.Client localClient container.Client
localIPs []string localIPs []string
name string name string
timeout time.Duration timeout time.Duration
@@ -47,7 +47,7 @@ func localIPs() []string {
return ips return ips
} }
func NewSwarmClientManager(localClient docker.Client, certs tls.Certificate, timeout time.Duration, agentManager *RetriableClientManager, filter docker.ContainerFilter) *SwarmClientManager { func NewSwarmClientManager(localClient container.Client, certs tls.Certificate, timeout time.Duration, agentManager *RetriableClientManager, filter container.ContainerFilter) *SwarmClientManager {
clientMap := make(map[string]ClientService) clientMap := make(map[string]ClientService)
localService := NewDockerClientService(localClient, filter) localService := NewDockerClientService(localClient, filter)
clientMap[localClient.Host().ID] = localService clientMap[localClient.Host().ID] = localService
@@ -59,12 +59,12 @@ func NewSwarmClientManager(localClient docker.Client, certs tls.Certificate, tim
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
container, err := localClient.FindContainer(ctx, id) c, err := localClient.FindContainer(ctx, id)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("error finding own container when looking for swarm service name") log.Fatal().Err(err).Msg("error finding own container when looking for swarm service name")
} }
serviceName := container.Labels["com.docker.swarm.service.name"] serviceName := c.Labels["com.docker.swarm.service.name"]
log.Debug().Str("service", serviceName).Msg("found swarm service name") log.Debug().Str("service", serviceName).Msg("found swarm service name")
@@ -72,7 +72,7 @@ func NewSwarmClientManager(localClient docker.Client, certs tls.Certificate, tim
localClient: localClient, localClient: localClient,
clients: clientMap, clients: clientMap,
certs: certs, certs: certs,
subscribers: xsync.NewMapOf[context.Context, chan<- docker.Host](), subscribers: xsync.NewMapOf[context.Context, chan<- container.Host](),
localIPs: localIPs(), localIPs: localIPs(),
name: serviceName, name: serviceName,
timeout: timeout, timeout: timeout,
@@ -80,7 +80,7 @@ func NewSwarmClientManager(localClient docker.Client, certs tls.Certificate, tim
} }
} }
func (m *SwarmClientManager) Subscribe(ctx context.Context, channel chan<- docker.Host) { func (m *SwarmClientManager) Subscribe(ctx context.Context, channel chan<- container.Host) {
m.subscribers.Store(ctx, channel) m.subscribers.Store(ctx, channel)
m.agentManager.Subscribe(ctx, channel) m.agentManager.Subscribe(ctx, channel)
@@ -159,7 +159,7 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
m.clients[host.ID] = client m.clients[host.ID] = client
log.Info().Stringer("ip", ip).Str("id", host.ID).Str("name", host.Name).Msg("added new swarm agent") log.Info().Stringer("ip", ip).Str("id", host.ID).Str("name", host.Name).Msg("added new swarm agent")
m.subscribers.Range(func(ctx context.Context, channel chan<- docker.Host) bool { m.subscribers.Range(func(ctx context.Context, channel chan<- container.Host) bool {
host.Available = true host.Available = true
host.Type = "swarm" host.Type = "swarm"
@@ -205,12 +205,12 @@ func (m *SwarmClientManager) Find(id string) (ClientService, bool) {
return client, ok return client, ok
} }
func (m *SwarmClientManager) Hosts(ctx context.Context) []docker.Host { func (m *SwarmClientManager) Hosts(ctx context.Context) []container.Host {
m.mu.RLock() m.mu.RLock()
clients := lo.Values(m.clients) clients := lo.Values(m.clients)
m.mu.RUnlock() m.mu.RUnlock()
swarmNodes := lop.Map(clients, func(client ClientService, _ int) docker.Host { swarmNodes := lop.Map(clients, func(client ClientService, _ int) container.Host {
host, err := client.Host(ctx) host, err := client.Host(ctx)
if err != nil { if err != nil {
log.Warn().Err(err).Str("id", host.ID).Msg("error getting host from client") log.Warn().Err(err).Str("id", host.ID).Msg("error getting host from client")
@@ -230,6 +230,6 @@ func (m *SwarmClientManager) String() string {
return fmt.Sprintf("SwarmClientManager{clients: %d}", len(m.clients)) return fmt.Sprintf("SwarmClientManager{clients: %d}", len(m.clients))
} }
func (m *SwarmClientManager) LocalClients() []docker.Client { func (m *SwarmClientManager) LocalClients() []container.Client {
return []docker.Client{m.localClient} return []container.Client{m.localClient}
} }

View File

@@ -5,7 +5,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
orderedmap "github.com/wk8/go-ordered-map/v2" orderedmap "github.com/wk8/go-ordered-map/v2"
) )
@@ -26,7 +26,7 @@ func ParseRegex(search string) (*regexp.Regexp, error) {
return re, nil return re, nil
} }
func Search(re *regexp.Regexp, logEvent *docker.LogEvent) bool { func Search(re *regexp.Regexp, logEvent *container.LogEvent) bool {
switch value := logEvent.Message.(type) { switch value := logEvent.Message.(type) {
case string: case string:
if re.MatchString(value) { if re.MatchString(value) {

View File

@@ -6,7 +6,7 @@ import (
"time" "time"
"github.com/amir20/dozzle/internal/auth" "github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -30,7 +30,7 @@ func (h *handler) containerActions(w http.ResponseWriter, r *http.Request) {
return return
} }
parsedAction, err := docker.ParseContainerAction(action) parsedAction, err := container.ParseContainerAction(action)
if err != nil { if err != nil {
log.Error().Err(err).Msg("error while trying to parse action") log.Error().Err(err).Msg("error while trying to parse action")
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)

View File

@@ -7,7 +7,7 @@ import (
"testing" "testing"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -15,17 +15,17 @@ import (
func mockedClient() *MockedClient { func mockedClient() *MockedClient {
mockedClient := new(MockedClient) mockedClient := new(MockedClient)
container := docker.Container{ID: "123"} c := container.Container{ID: "123"}
mockedClient.On("FindContainer", mock.Anything, "123").Return(container, nil) mockedClient.On("FindContainer", mock.Anything, "123").Return(c, nil)
mockedClient.On("FindContainer", mock.Anything, "456").Return(docker.Container{}, errors.New("container not found")) mockedClient.On("FindContainer", mock.Anything, "456").Return(container.Container{}, errors.New("container not found"))
mockedClient.On("ContainerActions", mock.Anything, docker.Start, container.ID).Return(nil) mockedClient.On("ContainerActions", mock.Anything, container.Start, c.ID).Return(nil)
mockedClient.On("ContainerActions", mock.Anything, docker.Stop, container.ID).Return(nil) mockedClient.On("ContainerActions", mock.Anything, container.Stop, c.ID).Return(nil)
mockedClient.On("ContainerActions", mock.Anything, docker.Restart, container.ID).Return(nil) mockedClient.On("ContainerActions", mock.Anything, container.Restart, c.ID).Return(nil)
mockedClient.On("ContainerActions", mock.Anything, docker.Start, mock.Anything).Return(errors.New("container not found")) mockedClient.On("ContainerActions", mock.Anything, container.Start, mock.Anything).Return(errors.New("container not found"))
mockedClient.On("ContainerActions", mock.Anything, docker.ContainerAction("something-else"), container.ID).Return(errors.New("unknown action")) mockedClient.On("ContainerActions", mock.Anything, container.ContainerAction("something-else"), c.ID).Return(errors.New("unknown action"))
mockedClient.On("Host").Return(docker.Host{ID: "localhost"}) mockedClient.On("Host").Return(container.Host{ID: "localhost"})
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{container}, nil) mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{c}, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.Anything).Return(nil) mockedClient.On("ContainerEvents", mock.Anything, mock.Anything).Return(nil)
return mockedClient return mockedClient

View File

@@ -4,13 +4,13 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
) )
func (h *handler) debugStore(w http.ResponseWriter, r *http.Request) { func (h *handler) debugStore(w http.ResponseWriter, r *http.Request) {
respone := make(map[string]interface{}) respone := make(map[string]interface{})
respone["hosts"] = h.multiHostService.Hosts() respone["hosts"] = h.multiHostService.Hosts()
containers, errors := h.multiHostService.ListAllContainers(docker.ContainerFilter{}) containers, errors := h.multiHostService.ListAllContainers(container.ContainerFilter{})
respone["containers"] = containers respone["containers"] = containers
respone["errors"] = errors respone["errors"] = errors

View File

@@ -9,7 +9,7 @@ import (
"time" "time"
"github.com/amir20/dozzle/internal/auth" "github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/stdcopy"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )
@@ -32,12 +32,12 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
now := time.Now() now := time.Now()
nowFmt := now.Format("2006-01-02T15-04-05") nowFmt := now.Format("2006-01-02T15-04-05")
var stdTypes docker.StdType var stdTypes container.StdType
if r.URL.Query().Has("stdout") { if r.URL.Query().Has("stdout") {
stdTypes |= docker.STDOUT stdTypes |= container.STDOUT
} }
if r.URL.Query().Has("stderr") { if r.URL.Query().Has("stderr") {
stdTypes |= docker.STDERR stdTypes |= container.STDERR
} }
if stdTypes == 0 { if stdTypes == 0 {

View File

@@ -9,7 +9,7 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -21,17 +21,17 @@ func Test_handler_download_logs(t *testing.T) {
mockedClient := new(MockedClient) mockedClient := new(MockedClient)
data := makeMessage("INFO Testing logs...", docker.STDOUT) data := makeMessage("INFO Testing logs...", container.STDOUT)
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.Container{ID: id, Tty: false}, nil) mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Tty: false}, nil)
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, mock.Anything, mock.Anything, docker.STDOUT).Return(io.NopCloser(bytes.NewReader(data)), nil) mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, mock.Anything, mock.Anything, container.STDOUT).Return(io.NopCloser(bytes.NewReader(data)), nil)
mockedClient.On("Host").Return(docker.Host{ mockedClient.On("Host").Return(container.Host{
ID: "localhost", ID: "localhost",
}) })
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) { mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
}) })
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{ mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{ID: id, Name: "test", State: "running"}, {ID: id, Name: "test", State: "running"},
}, nil) }, nil)

View File

@@ -5,7 +5,7 @@ import (
"github.com/amir20/dozzle/internal/analytics" "github.com/amir20/dozzle/internal/analytics"
"github.com/amir20/dozzle/internal/auth" "github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
docker_support "github.com/amir20/dozzle/internal/support/docker" docker_support "github.com/amir20/dozzle/internal/support/docker"
support_web "github.com/amir20/dozzle/internal/support/web" support_web "github.com/amir20/dozzle/internal/support/web"
"github.com/amir20/dozzle/types" "github.com/amir20/dozzle/types"
@@ -20,9 +20,9 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
return return
} }
events := make(chan docker.ContainerEvent) events := make(chan container.ContainerEvent)
stats := make(chan docker.ContainerStat) stats := make(chan container.ContainerStat)
availableHosts := make(chan docker.Host) availableHosts := make(chan container.Host)
h.multiHostService.SubscribeEventsAndStats(r.Context(), events, stats) h.multiHostService.SubscribeEventsAndStats(r.Context(), events, stats)
h.multiHostService.SubscribeAvailableHosts(r.Context(), availableHosts) h.multiHostService.SubscribeAvailableHosts(r.Context(), availableHosts)

View File

@@ -9,7 +9,7 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
docker_support "github.com/amir20/dozzle/internal/support/docker" docker_support "github.com/amir20/dozzle/internal/support/docker"
"github.com/amir20/dozzle/internal/utils" "github.com/amir20/dozzle/internal/utils"
"github.com/beme/abide" "github.com/beme/abide"
@@ -24,17 +24,17 @@ func Test_handler_streamEvents_happy(t *testing.T) {
mockedClient := new(MockedClient) mockedClient := new(MockedClient)
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{}, nil) mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{}, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) { mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
messages := args.Get(1).(chan<- docker.ContainerEvent) messages := args.Get(1).(chan<- container.ContainerEvent)
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
messages <- docker.ContainerEvent{ messages <- container.ContainerEvent{
Name: "start", Name: "start",
ActorID: "1234", ActorID: "1234",
Host: "localhost", Host: "localhost",
} }
messages <- docker.ContainerEvent{ messages <- container.ContainerEvent{
Name: "something-random", Name: "something-random",
ActorID: "1234", ActorID: "1234",
Host: "localhost", Host: "localhost",
@@ -42,19 +42,19 @@ func Test_handler_streamEvents_happy(t *testing.T) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
cancel() cancel()
}) })
mockedClient.On("FindContainer", mock.Anything, "1234").Return(docker.Container{ mockedClient.On("FindContainer", mock.Anything, "1234").Return(container.Container{
ID: "1234", ID: "1234",
Name: "test", Name: "test",
Image: "test", Image: "test",
Stats: utils.NewRingBuffer[docker.ContainerStat](300), // 300 seconds of stats Stats: utils.NewRingBuffer[container.ContainerStat](300), // 300 seconds of stats
}, nil) }, nil)
mockedClient.On("Host").Return(docker.Host{ mockedClient.On("Host").Return(container.Host{
ID: "localhost", ID: "localhost",
}) })
// This is needed so that the server is initialized for store // This is needed so that the server is initialized for store
manager := docker_support.NewRetriableClientManager(nil, 3*time.Second, tls.Certificate{}, docker_support.NewDockerClientService(mockedClient, docker.ContainerFilter{})) manager := docker_support.NewRetriableClientManager(nil, 3*time.Second, tls.Certificate{}, docker_support.NewDockerClientService(mockedClient, container.ContainerFilter{}))
multiHostService := docker_support.NewMultiHostService(manager, 3*time.Second) multiHostService := docker_support.NewMultiHostService(manager, 3*time.Second)
server := CreateServer(multiHostService, nil, Config{Base: "/", Authorization: Authorization{Provider: NONE}}) server := CreateServer(multiHostService, nil, Config{Base: "/", Authorization: Authorization{Provider: NONE}})

View File

@@ -11,7 +11,7 @@ func (h *handler) healthcheck(w http.ResponseWriter, r *http.Request) {
clients := h.multiHostService.LocalClients() clients := h.multiHostService.LocalClients()
for _, client := range clients { for _, client := range clients {
if _, err := client.Ping(r.Context()); err != nil { if err := client.Ping(r.Context()); err != nil {
log.Error().Err(err).Str("host", client.Host().Name).Msg("error pinging host") log.Error().Err(err).Str("host", client.Host().Name).Msg("error pinging host")
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
} }

View File

@@ -17,7 +17,7 @@ import (
"time" "time"
"github.com/amir20/dozzle/internal/auth" "github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/support/search" "github.com/amir20/dozzle/internal/support/search"
support_web "github.com/amir20/dozzle/internal/support/web" support_web "github.com/amir20/dozzle/internal/support/web"
"github.com/amir20/dozzle/internal/utils" "github.com/amir20/dozzle/internal/utils"
@@ -34,12 +34,12 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
to, _ := time.Parse(time.RFC3339Nano, r.URL.Query().Get("to")) to, _ := time.Parse(time.RFC3339Nano, r.URL.Query().Get("to"))
id := chi.URLParam(r, "id") id := chi.URLParam(r, "id")
var stdTypes docker.StdType var stdTypes container.StdType
if r.URL.Query().Has("stdout") { if r.URL.Query().Has("stdout") {
stdTypes |= docker.STDOUT stdTypes |= container.STDOUT
} }
if r.URL.Query().Has("stderr") { if r.URL.Query().Has("stderr") {
stdTypes |= docker.STDERR stdTypes |= container.STDERR
} }
if stdTypes == 0 { if stdTypes == 0 {
@@ -61,7 +61,7 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
return return
} }
buffer := utils.NewRingBuffer[*docker.LogEvent](500) buffer := utils.NewRingBuffer[*container.LogEvent](500)
delta := max(to.Sub(from), time.Second*3) delta := max(to.Sub(from), time.Second*3)
var regex *regexp.Regexp var regex *regexp.Regexp
@@ -179,7 +179,7 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
func (h *handler) streamContainerLogs(w http.ResponseWriter, r *http.Request) { func (h *handler) streamContainerLogs(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id") id := chi.URLParam(r, "id")
h.streamLogsForContainers(w, r, func(container *docker.Container) bool { h.streamLogsForContainers(w, r, func(container *container.Container) bool {
return container.ID == id && container.Host == hostKey(r) return container.ID == id && container.Host == hostKey(r)
}) })
} }
@@ -192,14 +192,14 @@ func (h *handler) streamLogsMerged(w http.ResponseWriter, r *http.Request) {
ids[id] = true ids[id] = true
} }
h.streamLogsForContainers(w, r, func(container *docker.Container) bool { h.streamLogsForContainers(w, r, func(container *container.Container) bool {
return ids[container.ID] && container.Host == hostKey(r) return ids[container.ID] && container.Host == hostKey(r)
}) })
} }
func (h *handler) streamServiceLogs(w http.ResponseWriter, r *http.Request) { func (h *handler) streamServiceLogs(w http.ResponseWriter, r *http.Request) {
service := chi.URLParam(r, "service") service := chi.URLParam(r, "service")
h.streamLogsForContainers(w, r, func(container *docker.Container) bool { h.streamLogsForContainers(w, r, func(container *container.Container) bool {
return container.State == "running" && container.Labels["com.docker.swarm.service.name"] == service return container.State == "running" && container.Labels["com.docker.swarm.service.name"] == service
}) })
} }
@@ -207,7 +207,7 @@ func (h *handler) streamServiceLogs(w http.ResponseWriter, r *http.Request) {
func (h *handler) streamGroupedLogs(w http.ResponseWriter, r *http.Request) { func (h *handler) streamGroupedLogs(w http.ResponseWriter, r *http.Request) {
group := chi.URLParam(r, "group") group := chi.URLParam(r, "group")
h.streamLogsForContainers(w, r, func(container *docker.Container) bool { h.streamLogsForContainers(w, r, func(container *container.Container) bool {
return container.State == "running" && container.Group == group return container.State == "running" && container.Group == group
}) })
} }
@@ -215,25 +215,25 @@ func (h *handler) streamGroupedLogs(w http.ResponseWriter, r *http.Request) {
func (h *handler) streamStackLogs(w http.ResponseWriter, r *http.Request) { func (h *handler) streamStackLogs(w http.ResponseWriter, r *http.Request) {
stack := chi.URLParam(r, "stack") stack := chi.URLParam(r, "stack")
h.streamLogsForContainers(w, r, func(container *docker.Container) bool { h.streamLogsForContainers(w, r, func(container *container.Container) bool {
return container.State == "running" && container.Labels["com.docker.stack.namespace"] == stack return container.State == "running" && container.Labels["com.docker.stack.namespace"] == stack
}) })
} }
func (h *handler) streamHostLogs(w http.ResponseWriter, r *http.Request) { func (h *handler) streamHostLogs(w http.ResponseWriter, r *http.Request) {
host := hostKey(r) host := hostKey(r)
h.streamLogsForContainers(w, r, func(container *docker.Container) bool { h.streamLogsForContainers(w, r, func(container *container.Container) bool {
return container.State == "running" && container.Host == host return container.State == "running" && container.Host == host
}) })
} }
func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request, containerFilter ContainerFilter) { func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request, containerFilter ContainerFilter) {
var stdTypes docker.StdType var stdTypes container.StdType
if r.URL.Query().Has("stdout") { if r.URL.Query().Has("stdout") {
stdTypes |= docker.STDOUT stdTypes |= container.STDOUT
} }
if r.URL.Query().Has("stderr") { if r.URL.Query().Has("stderr") {
stdTypes |= docker.STDERR stdTypes |= container.STDERR
} }
if stdTypes == 0 { if stdTypes == 0 {
@@ -263,9 +263,9 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
absoluteTime := time.Time{} absoluteTime := time.Time{}
var regex *regexp.Regexp var regex *regexp.Regexp
liveLogs := make(chan *docker.LogEvent) liveLogs := make(chan *container.LogEvent)
events := make(chan *docker.ContainerEvent, 1) events := make(chan *container.ContainerEvent, 1)
backfill := make(chan []*docker.LogEvent) backfill := make(chan []*container.LogEvent)
levels := make(map[string]struct{}) levels := make(map[string]struct{})
for _, level := range r.URL.Query()["levels"] { for _, level := range r.URL.Query()["levels"] {
@@ -287,7 +287,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
delta := -10 * time.Second delta := -10 * time.Second
to := absoluteTime to := absoluteTime
for minimum > 0 { for minimum > 0 {
events := make([]*docker.LogEvent, 0) events := make([]*container.LogEvent, 0)
stillRunning := false stillRunning := false
for _, container := range existingContainers { for _, container := range existingContainers {
containerService, err := h.multiHostService.FindContainer(container.Host, container.ID, usersFilter) containerService, err := h.multiHostService.FindContainer(container.Host, container.ID, usersFilter)
@@ -336,30 +336,30 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
}() }()
} }
streamLogs := func(container docker.Container) { streamLogs := func(c container.Container) {
containerService, err := h.multiHostService.FindContainer(container.Host, container.ID, usersFilter) containerService, err := h.multiHostService.FindContainer(c.Host, c.ID, usersFilter)
if err != nil { if err != nil {
log.Error().Err(err).Msg("error while finding container") log.Error().Err(err).Msg("error while finding container")
return return
} }
container = containerService.Container c = containerService.Container
start := utils.Max(absoluteTime, container.StartedAt) start := utils.Max(absoluteTime, c.StartedAt)
err = containerService.StreamLogs(r.Context(), start, stdTypes, liveLogs) err = containerService.StreamLogs(r.Context(), start, stdTypes, liveLogs)
if err != nil { if err != nil {
if errors.Is(err, io.EOF) { if errors.Is(err, io.EOF) {
log.Debug().Str("container", container.ID).Msg("streaming ended") log.Debug().Str("container", c.ID).Msg("streaming ended")
finishedAt := container.FinishedAt finishedAt := c.FinishedAt
if container.FinishedAt.IsZero() { if c.FinishedAt.IsZero() {
finishedAt = time.Now() finishedAt = time.Now()
} }
events <- &docker.ContainerEvent{ events <- &container.ContainerEvent{
ActorID: container.ID, ActorID: c.ID,
Name: "container-stopped", Name: "container-stopped",
Host: container.Host, Host: c.Host,
Time: finishedAt, Time: finishedAt,
} }
} else if !errors.Is(err, context.Canceled) { } else if !errors.Is(err, context.Canceled) {
log.Error().Err(err).Str("container", container.ID).Msg("unknown error while streaming logs") log.Error().Err(err).Str("container", c.ID).Msg("unknown error while streaming logs")
} }
} }
} }
@@ -368,7 +368,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
go streamLogs(container) go streamLogs(container)
} }
newContainers := make(chan docker.Container) newContainers := make(chan container.Container)
h.multiHostService.SubscribeContainersStarted(r.Context(), newContainers, containerFilter) h.multiHostService.SubscribeContainersStarted(r.Context(), newContainers, containerFilter)
ticker := time.NewTicker(5 * time.Second) ticker := time.NewTicker(5 * time.Second)
@@ -387,10 +387,10 @@ loop:
continue continue
} }
sseWriter.Message(logEvent) sseWriter.Message(logEvent)
case container := <-newContainers: case c := <-newContainers:
if _, err := h.multiHostService.FindContainer(container.Host, container.ID, usersFilter); err == nil { if _, err := h.multiHostService.FindContainer(c.Host, c.ID, usersFilter); err == nil {
events <- &docker.ContainerEvent{ActorID: container.ID, Name: "container-started", Host: container.Host} events <- &container.ContainerEvent{ActorID: c.ID, Name: "container-started", Host: c.Host}
go streamLogs(container) go streamLogs(c)
} }
case event := <-events: case event := <-events:

View File

@@ -14,7 +14,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
"github.com/beme/abide" "github.com/beme/abide"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -36,25 +36,25 @@ func Test_handler_streamLogs_happy(t *testing.T) {
mockedClient := new(MockedClient) mockedClient := new(MockedClient)
data := makeMessage("INFO Testing logs...", docker.STDOUT) data := makeMessage("INFO Testing logs...", container.STDOUT)
now := time.Now() now := time.Now()
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.Container{ID: id, Tty: false, Host: "localhost", StartedAt: now}, nil) mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Tty: false, Host: "localhost", StartedAt: now}, nil)
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, now, docker.STDALL).Return(io.NopCloser(bytes.NewReader(data)), nil). mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, now, container.STDALL).Return(io.NopCloser(bytes.NewReader(data)), nil).
Run(func(args mock.Arguments) { Run(func(args mock.Arguments) {
go func() { go func() {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
cancel() cancel()
}() }()
}) })
mockedClient.On("Host").Return(docker.Host{ mockedClient.On("Host").Return(container.Host{
ID: "localhost", ID: "localhost",
}) })
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{ mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{ID: id, Name: "test", Host: "localhost", State: "running"}, {ID: id, Name: "test", Host: "localhost", State: "running"},
}, nil) }, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) { mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
}) })
@@ -80,27 +80,27 @@ func Test_handler_streamLogs_happy_with_id(t *testing.T) {
mockedClient := new(MockedClient) mockedClient := new(MockedClient)
data := makeMessage("2020-05-13T18:55:37.772853839Z INFO Testing logs...", docker.STDOUT) data := makeMessage("2020-05-13T18:55:37.772853839Z INFO Testing logs...", container.STDOUT)
started := time.Date(2020, time.May, 13, 18, 55, 37, 772853839, time.UTC) started := time.Date(2020, time.May, 13, 18, 55, 37, 772853839, time.UTC)
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.Container{ID: id, Host: "localhost", StartedAt: started}, nil) mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Host: "localhost", StartedAt: started}, nil)
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, started, docker.STDALL).Return(io.NopCloser(bytes.NewReader(data)), nil). mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, started, container.STDALL).Return(io.NopCloser(bytes.NewReader(data)), nil).
Run(func(args mock.Arguments) { Run(func(args mock.Arguments) {
go func() { go func() {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
cancel() cancel()
}() }()
}) })
mockedClient.On("Host").Return(docker.Host{ mockedClient.On("Host").Return(container.Host{
ID: "localhost", ID: "localhost",
}) })
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{ mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{ID: id, Name: "test", Host: "localhost", State: "running"}, {ID: id, Name: "test", Host: "localhost", State: "running"},
}, nil) }, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) { mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
}) })
@@ -125,21 +125,21 @@ func Test_handler_streamLogs_happy_container_stopped(t *testing.T) {
started := time.Date(2020, time.May, 13, 18, 55, 37, 772853839, time.UTC) started := time.Date(2020, time.May, 13, 18, 55, 37, 772853839, time.UTC)
mockedClient := new(MockedClient) mockedClient := new(MockedClient)
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.Container{ID: id, Host: "localhost", StartedAt: started}, nil) mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Host: "localhost", StartedAt: started}, nil)
mockedClient.On("ContainerLogs", mock.Anything, id, started, docker.STDALL).Return(io.NopCloser(strings.NewReader("")), io.EOF). mockedClient.On("ContainerLogs", mock.Anything, id, started, container.STDALL).Return(io.NopCloser(strings.NewReader("")), io.EOF).
Run(func(args mock.Arguments) { Run(func(args mock.Arguments) {
go func() { go func() {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
cancel() cancel()
}() }()
}) })
mockedClient.On("Host").Return(docker.Host{ mockedClient.On("Host").Return(container.Host{
ID: "localhost", ID: "localhost",
}) })
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{ mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{ID: id, Name: "test", Host: "localhost", State: "running"}, {ID: id, Name: "test", Host: "localhost", State: "running"},
}, nil) }, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil) mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil)
handler := createDefaultHandler(mockedClient) handler := createDefaultHandler(mockedClient)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@@ -163,21 +163,21 @@ func Test_handler_streamLogs_error_reading(t *testing.T) {
started := time.Date(2020, time.May, 13, 18, 55, 37, 772853839, time.UTC) started := time.Date(2020, time.May, 13, 18, 55, 37, 772853839, time.UTC)
mockedClient := new(MockedClient) mockedClient := new(MockedClient)
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.Container{ID: id, Host: "localhost", StartedAt: started}, nil) mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Host: "localhost", StartedAt: started}, nil)
mockedClient.On("ContainerLogs", mock.Anything, id, started, docker.STDALL).Return(io.NopCloser(strings.NewReader("")), errors.New("test error")). mockedClient.On("ContainerLogs", mock.Anything, id, started, container.STDALL).Return(io.NopCloser(strings.NewReader("")), errors.New("test error")).
Run(func(args mock.Arguments) { Run(func(args mock.Arguments) {
go func() { go func() {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
cancel() cancel()
}() }()
}) })
mockedClient.On("Host").Return(docker.Host{ mockedClient.On("Host").Return(container.Host{
ID: "localhost", ID: "localhost",
}) })
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{ mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{ID: id, Name: "test", Host: "localhost", State: "running"}, {ID: id, Name: "test", Host: "localhost", State: "running"},
}, nil) }, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil) mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil)
handler := createDefaultHandler(mockedClient) handler := createDefaultHandler(mockedClient)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@@ -194,14 +194,14 @@ func Test_handler_streamLogs_error_std(t *testing.T) {
require.NoError(t, err, "NewRequest should not return an error.") require.NoError(t, err, "NewRequest should not return an error.")
mockedClient := new(MockedClient) mockedClient := new(MockedClient)
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.Container{ID: id, Host: "localhost"}, nil) mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Host: "localhost"}, nil)
mockedClient.On("Host").Return(docker.Host{ mockedClient.On("Host").Return(container.Host{
ID: "localhost", ID: "localhost",
}) })
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{ mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{ID: id, Name: "test", Host: "localhost", State: "running"}, {ID: id, Name: "test", Host: "localhost", State: "running"},
}, nil) }, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil). mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).
Run(func(args mock.Arguments) { Run(func(args mock.Arguments) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
}) })
@@ -231,19 +231,19 @@ func Test_handler_between_dates(t *testing.T) {
mockedClient := new(MockedClient) mockedClient := new(MockedClient)
first := makeMessage("2020-05-13T18:55:37.772853839Z INFO Testing stdout logs...\n", docker.STDOUT) first := makeMessage("2020-05-13T18:55:37.772853839Z INFO Testing stdout logs...\n", container.STDOUT)
second := makeMessage("2020-05-13T18:56:37.772853839Z INFO Testing stderr logs...\n", docker.STDERR) second := makeMessage("2020-05-13T18:56:37.772853839Z INFO Testing stderr logs...\n", container.STDERR)
data := append(first, second...) data := append(first, second...)
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, from, to, docker.STDALL).Return(io.NopCloser(bytes.NewReader(data)), nil) mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, from, to, container.STDALL).Return(io.NopCloser(bytes.NewReader(data)), nil)
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.Container{ID: id}, nil) mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id}, nil)
mockedClient.On("Host").Return(docker.Host{ mockedClient.On("Host").Return(container.Host{
ID: "localhost", ID: "localhost",
}) })
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{ mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{ID: id, Name: "test", Host: "localhost", State: "running"}, {ID: id, Name: "test", Host: "localhost", State: "running"},
}, nil) }, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil) mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil)
handler := createDefaultHandler(mockedClient) handler := createDefaultHandler(mockedClient)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@@ -273,24 +273,24 @@ func Test_handler_between_dates_with_fill(t *testing.T) {
mockedClient := new(MockedClient) mockedClient := new(MockedClient)
first := makeMessage("2020-05-13T18:55:37.772853839Z INFO Testing stdout logs...\n", docker.STDOUT) first := makeMessage("2020-05-13T18:55:37.772853839Z INFO Testing stdout logs...\n", container.STDOUT)
second := makeMessage("2020-05-13T18:56:37.772853839Z INFO Testing stderr logs...\n", docker.STDERR) second := makeMessage("2020-05-13T18:56:37.772853839Z INFO Testing stderr logs...\n", container.STDERR)
data := append(first, second...) data := append(first, second...)
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, from, to, docker.STDALL). mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, from, to, container.STDALL).
Return(io.NopCloser(bytes.NewReader([]byte{})), nil). Return(io.NopCloser(bytes.NewReader([]byte{})), nil).
Once() Once()
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, time.Date(2017, time.December, 31, 14, 0, 0, 0, time.UTC), to, docker.STDALL). mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, time.Date(2017, time.December, 31, 14, 0, 0, 0, time.UTC), to, container.STDALL).
Return(io.NopCloser(bytes.NewReader(data)), nil). Return(io.NopCloser(bytes.NewReader(data)), nil).
Once() Once()
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.Container{ID: id}, nil) mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id}, nil)
mockedClient.On("Host").Return(docker.Host{ mockedClient.On("Host").Return(container.Host{
ID: "localhost", ID: "localhost",
}) })
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{ mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{ID: id, Name: "test", Host: "localhost", State: "running"}, {ID: id, Name: "test", Host: "localhost", State: "running"},
}, nil) }, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil) mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil)
handler := createDefaultHandler(mockedClient) handler := createDefaultHandler(mockedClient)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
@@ -300,7 +300,7 @@ func Test_handler_between_dates_with_fill(t *testing.T) {
mockedClient.AssertExpectations(t) mockedClient.AssertExpectations(t)
} }
func makeMessage(message string, stream docker.StdType) []byte { func makeMessage(message string, stream container.StdType) []byte {
data := make([]byte, 8) data := make([]byte, 8)
binary.BigEndian.PutUint32(data[4:], uint32(len(message))) binary.BigEndian.PutUint32(data[4:], uint32(len(message)))
data[0] = byte(stream / 2) data[0] = byte(stream / 2)

View File

@@ -8,7 +8,7 @@ import (
"strings" "strings"
"github.com/amir20/dozzle/internal/auth" "github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
docker_support "github.com/amir20/dozzle/internal/support/docker" docker_support "github.com/amir20/dozzle/internal/support/docker"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@@ -34,7 +34,7 @@ type Config struct {
Dev bool Dev bool
Authorization Authorization Authorization Authorization
EnableActions bool EnableActions bool
Filter docker.ContainerFilter Filter container.ContainerFilter
} }
type Authorization struct { type Authorization struct {

View File

@@ -8,7 +8,7 @@ import (
"io" "io"
"io/fs" "io/fs"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/container"
docker_support "github.com/amir20/dozzle/internal/support/docker" docker_support "github.com/amir20/dozzle/internal/support/docker"
"github.com/docker/docker/api/types/system" "github.com/docker/docker/api/types/system"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@@ -20,46 +20,46 @@ import (
type MockedClient struct { type MockedClient struct {
mock.Mock mock.Mock
docker.Client container.Client
} }
func (m *MockedClient) FindContainer(ctx context.Context, id string) (docker.Container, error) { func (m *MockedClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
args := m.Called(ctx, id) args := m.Called(ctx, id)
return args.Get(0).(docker.Container), args.Error(1) return args.Get(0).(container.Container), args.Error(1)
} }
func (m *MockedClient) ContainerActions(ctx context.Context, action docker.ContainerAction, containerID string) error { func (m *MockedClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error {
args := m.Called(ctx, action, containerID) args := m.Called(ctx, action, containerID)
return args.Error(0) return args.Error(0)
} }
func (m *MockedClient) ContainerEvents(ctx context.Context, events chan<- docker.ContainerEvent) error { func (m *MockedClient) ContainerEvents(ctx context.Context, events chan<- container.ContainerEvent) error {
args := m.Called(ctx, events) args := m.Called(ctx, events)
return args.Error(0) return args.Error(0)
} }
func (m *MockedClient) ListContainers(ctx context.Context, filter docker.ContainerFilter) ([]docker.Container, error) { func (m *MockedClient) ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error) {
args := m.Called(ctx, filter) args := m.Called(ctx, filter)
return args.Get(0).([]docker.Container), args.Error(1) return args.Get(0).([]container.Container), args.Error(1)
} }
func (m *MockedClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType docker.StdType) (io.ReadCloser, error) { func (m *MockedClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType container.StdType) (io.ReadCloser, error) {
args := m.Called(ctx, id, since, stdType) args := m.Called(ctx, id, since, stdType)
return args.Get(0).(io.ReadCloser), args.Error(1) return args.Get(0).(io.ReadCloser), args.Error(1)
} }
func (m *MockedClient) ContainerStats(context.Context, string, chan<- docker.ContainerStat) error { func (m *MockedClient) ContainerStats(context.Context, string, chan<- container.ContainerStat) error {
return nil return nil
} }
func (m *MockedClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType docker.StdType) (io.ReadCloser, error) { func (m *MockedClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType container.StdType) (io.ReadCloser, error) {
args := m.Called(ctx, id, from, to, stdType) args := m.Called(ctx, id, from, to, stdType)
return args.Get(0).(io.ReadCloser), args.Error(1) return args.Get(0).(io.ReadCloser), args.Error(1)
} }
func (m *MockedClient) Host() docker.Host { func (m *MockedClient) Host() container.Host {
args := m.Called() args := m.Called()
return args.Get(0).(docker.Host) return args.Get(0).(container.Host)
} }
func (m *MockedClient) IsSwarmMode() bool { func (m *MockedClient) IsSwarmMode() bool {
@@ -70,14 +70,14 @@ func (m *MockedClient) SystemInfo() system.Info {
return system.Info{ID: "123"} return system.Info{ID: "123"}
} }
func createHandler(client docker.Client, content fs.FS, config Config) *chi.Mux { func createHandler(client container.Client, content fs.FS, config Config) *chi.Mux {
if client == nil { if client == nil {
client = new(MockedClient) client = new(MockedClient)
client.(*MockedClient).On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{}, nil) client.(*MockedClient).On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{}, nil)
client.(*MockedClient).On("Host").Return(docker.Host{ client.(*MockedClient).On("Host").Return(container.Host{
ID: "localhost", ID: "localhost",
}) })
client.(*MockedClient).On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil) client.(*MockedClient).On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil)
} }
if content == nil { if content == nil {
@@ -86,7 +86,7 @@ func createHandler(client docker.Client, content fs.FS, config Config) *chi.Mux
content = afero.NewIOFS(fs) content = afero.NewIOFS(fs)
} }
manager := docker_support.NewRetriableClientManager(nil, 3*time.Second, tls.Certificate{}, docker_support.NewDockerClientService(client, docker.ContainerFilter{})) manager := docker_support.NewRetriableClientManager(nil, 3*time.Second, tls.Certificate{}, docker_support.NewDockerClientService(client, container.ContainerFilter{}))
multiHostService := docker_support.NewMultiHostService(manager, 3*time.Second) multiHostService := docker_support.NewMultiHostService(manager, 3*time.Second)
return createRouter(&handler{ return createRouter(&handler{
multiHostService: multiHostService, multiHostService: multiHostService,
@@ -95,6 +95,6 @@ func createHandler(client docker.Client, content fs.FS, config Config) *chi.Mux
}) })
} }
func createDefaultHandler(client docker.Client) *chi.Mux { func createDefaultHandler(client container.Client) *chi.Mux {
return createHandler(client, nil, Config{Base: "/", Authorization: Authorization{Provider: NONE}}) return createHandler(client, nil, Config{Base: "/", Authorization: Authorization{Provider: NONE}})
} }

View File

@@ -16,6 +16,7 @@ import (
"github.com/amir20/dozzle/internal/agent" "github.com/amir20/dozzle/internal/agent"
"github.com/amir20/dozzle/internal/auth" "github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/healthcheck" "github.com/amir20/dozzle/internal/healthcheck"
"github.com/amir20/dozzle/internal/support/cli" "github.com/amir20/dozzle/internal/support/cli"
@@ -161,7 +162,7 @@ func main() {
var multiHostService *docker_support.MultiHostService var multiHostService *docker_support.MultiHostService
if args.Mode == "server" { if args.Mode == "server" {
var localClient docker.Client var localClient container.Client
localClient, multiHostService = cli.CreateMultiHostService(certs, args) localClient, multiHostService = cli.CreateMultiHostService(certs, args)
if multiHostService.TotalClients() == 0 { if multiHostService.TotalClients() == 0 {
log.Fatal().Msg("Could not connect to any Docker Engine") log.Fatal().Msg("Could not connect to any Docker Engine")