1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 13:23:07 +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"
"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/rs/zerolog/log"
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{
ContainerId: containerID,
Since: timestamppb.New(since),
@@ -101,7 +101,7 @@ func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since
return nil, err
}
events := make(chan *docker.LogEvent)
events := make(chan *container.LogEvent)
go func() {
sendLogs(stream, events)
@@ -111,7 +111,7 @@ func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since
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{
ContainerId: containerID,
Since: timestamppb.New(since),
@@ -125,7 +125,7 @@ func (c *Client) StreamContainerLogs(ctx context.Context, containerID string, si
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 {
resp, err := stream.Recv()
if err != nil {
@@ -151,19 +151,19 @@ func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- *docker.Log
continue
}
events <- &docker.LogEvent{
events <- &container.LogEvent{
Id: resp.Event.Id,
ContainerID: resp.Event.ContainerId,
Message: message,
Timestamp: resp.Event.Timestamp.AsTime().Unix(),
Position: docker.LogPosition(resp.Event.Position),
Position: container.LogPosition(resp.Event.Position),
Level: resp.Event.Level,
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{
ContainerId: containerID,
Since: timestamppb.New(since),
@@ -199,7 +199,7 @@ func (c *Client) StreamRawBytes(ctx context.Context, containerID string, since t
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{})
if err != nil {
return err
@@ -211,7 +211,7 @@ func (c *Client) StreamStats(ctx context.Context, stats chan<- docker.ContainerS
return rpcErrToErr(err)
}
stats <- docker.ContainerStat{
stats <- container.ContainerStat{
CPUPercent: resp.Stat.CpuPercent,
MemoryPercent: resp.Stat.MemoryPercent,
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{})
if err != nil {
return err
@@ -232,7 +232,7 @@ func (c *Client) StreamEvents(ctx context.Context, events chan<- docker.Containe
return rpcErrToErr(err)
}
events <- docker.ContainerEvent{
events <- container.ContainerEvent{
ActorID: resp.Event.ActorId,
Name: resp.Event.Name,
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{})
if err != nil {
return err
@@ -253,7 +253,7 @@ func (c *Client) StreamNewContainers(ctx context.Context, containers chan<- dock
return rpcErrToErr(err)
}
containers <- docker.Container{
containers <- container.Container{
ID: resp.Container.Id,
Name: resp.Container.Name,
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})
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 {
stats = append(stats, docker.ContainerStat{
stats = append(stats, container.ContainerStat{
ID: stat.Id,
CPUPercent: stat.CpuPercent,
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,
Name: response.Container.Name,
Image: response.Container.Image,
@@ -306,7 +306,7 @@ func (c *Client) FindContainer(ctx context.Context, containerID string) (docker.
}, 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{}
if filter != nil {
@@ -321,11 +321,11 @@ func (c *Client) ListContainers(ctx context.Context, filter docker.ContainerFilt
return nil, err
}
containers := make([]docker.Container, 0)
for _, container := range response.Containers {
var stats []docker.ContainerStat
for _, stat := range container.Stats {
stats = append(stats, docker.ContainerStat{
containers := make([]container.Container, 0)
for _, c := range response.Containers {
var stats []container.ContainerStat
for _, stat := range c.Stats {
stats = append(stats, container.ContainerStat{
ID: stat.Id,
CPUPercent: stat.CpuPercent,
MemoryPercent: stat.MemoryPercent,
@@ -333,20 +333,20 @@ func (c *Client) ListContainers(ctx context.Context, filter docker.ContainerFilt
})
}
containers = append(containers, docker.Container{
ID: container.Id,
Name: container.Name,
Image: container.Image,
Labels: container.Labels,
Group: container.Group,
Created: container.Created.AsTime(),
State: container.State,
Health: container.Health,
Host: container.Host,
Tty: container.Tty,
Command: container.Command,
StartedAt: container.Started.AsTime(),
FinishedAt: container.Finished.AsTime(),
containers = append(containers, container.Container{
ID: c.Id,
Name: c.Name,
Image: c.Image,
Labels: c.Labels,
Group: c.Group,
Created: c.Created.AsTime(),
State: c.State,
Health: c.Health,
Host: c.Host,
Tty: c.Tty,
Command: c.Command,
StartedAt: c.Started.AsTime(),
FinishedAt: c.Finished.AsTime(),
Stats: utils.RingBufferFrom(300, stats),
})
}
@@ -354,17 +354,17 @@ func (c *Client) ListContainers(ctx context.Context, filter docker.ContainerFilt
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{})
if err != nil {
return docker.Host{
return container.Host{
Endpoint: c.endpoint,
Type: "agent",
Available: false,
}, err
}
return docker.Host{
return container.Host{
ID: info.Host.Id,
Name: info.Host.Name,
NCPU: int(info.Host.CpuCores),
@@ -376,16 +376,16 @@ func (c *Client) Host(ctx context.Context) (docker.Host, error) {
}, 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
switch action {
case docker.Start:
case container.Start:
containerAction = pb.ContainerAction_Start
case docker.Stop:
case container.Stop:
containerAction = pb.ContainerAction_Stop
case docker.Restart:
case container.Restart:
containerAction = pb.ContainerAction_Restart
}

View File

@@ -10,7 +10,7 @@ import (
"testing"
"time"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/utils"
"github.com/docker/docker/api/types/system"
"github.com/stretchr/testify/assert"
@@ -28,46 +28,46 @@ var client *MockedClient
type MockedClient struct {
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)
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)
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)
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)
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)
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
}
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)
return args.Get(0).(io.ReadCloser), args.Error(1)
}
func (m *MockedClient) Host() docker.Host {
func (m *MockedClient) Host() container.Host {
args := m.Called()
return args.Get(0).(docker.Host)
return args.Get(0).(container.Host)
}
func (m *MockedClient) IsSwarmMode() bool {
@@ -92,7 +92,7 @@ func init() {
}
client = &MockedClient{}
client.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{
client.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{
ID: "123456",
Name: "test",
@@ -101,17 +101,17 @@ func init() {
},
}, nil)
client.On("Host").Return(docker.Host{
client.On("Host").Return(container.Host{
ID: "localhost",
Endpoint: "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)
})
client.On("FindContainer", mock.Anything, "123456").Return(docker.Container{
client.On("FindContainer", mock.Anything, "123456").Return(container.Container{
ID: "123456",
Name: "test",
Host: "localhost",
@@ -124,13 +124,13 @@ func init() {
Labels: map[string]string{
"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),
StartedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
FinishedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
}, nil)
server, _ := NewServer(client, certs, "test", docker.ContainerFilter{})
server, _ := NewServer(client, certs, "test", container.ContainerFilter{})
go server.Serve(lis)
}
@@ -145,9 +145,9 @@ func TestFindContainer(t *testing.T) {
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",
Name: "test",
Host: "localhost",
@@ -160,7 +160,7 @@ func TestFindContainer(t *testing.T) {
Labels: map[string]string{
"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),
StartedAt: 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)
}
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",
Name: "test",
@@ -189,7 +189,7 @@ func TestListContainers(t *testing.T) {
Labels: map[string]string{
"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),
StartedAt: 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"
"github.com/amir20/dozzle/internal/agent/pb"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
"github.com/rs/zerolog/log"
orderedmap "github.com/wk8/go-ordered-map/v2"
"google.golang.org/grpc"
@@ -25,19 +25,19 @@ import (
)
type server struct {
client docker.Client
store *docker.ContainerStore
client container.Client
store *container.ContainerStore
version string
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{
client: client,
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()
}
container, err := s.store.FindContainer(in.ContainerId, docker.ContainerFilter{})
c, err := s.store.FindContainer(in.ContainerId, container.ContainerFilter{})
if err != nil {
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 {
return err
}
g := docker.NewEventGenerator(out.Context(), reader, container)
g := container.NewEventGenerator(out.Context(), reader, c)
for event := range g.Events {
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 {
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 {
return err
}
container, err := s.client.FindContainer(out.Context(), in.ContainerId)
c, err := s.client.FindContainer(out.Context(), in.ContainerId)
if err != nil {
return err
}
g := docker.NewEventGenerator(out.Context(), reader, container)
g := container.NewEventGenerator(out.Context(), reader, c)
for {
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 {
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 {
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 {
events := make(chan docker.ContainerEvent)
events := make(chan container.ContainerEvent)
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 {
stats := make(chan docker.ContainerStat)
stats := make(chan container.ContainerStat)
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) {
filter := make(docker.ContainerFilter)
filter := make(container.ContainerFilter)
if in.GetFilter() != nil {
for k, v := range in.GetFilter() {
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) {
filter := make(docker.ContainerFilter)
filter := make(container.ContainerFilter)
if in.GetFilter() != nil {
for k, v := range in.GetFilter() {
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 {
containers := make(chan docker.Container)
containers := make(chan container.Container)
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) {
var action docker.ContainerAction
var action container.ContainerAction
switch in.Action {
case pb.ContainerAction_Start:
action = docker.Start
action = container.Start
case pb.ContainerAction_Stop:
action = docker.Stop
action = container.Stop
case pb.ContainerAction_Restart:
action = docker.Restart
action = container.Restart
default:
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
}
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()
c, err := x509.ParseCertificate(certificates.Certificate[0])
if err != nil {
@@ -346,7 +346,7 @@ func NewServer(client docker.Client, certificates tls.Certificate, dozzleVersion
return grpcServer, nil
}
func logEventToPb(event *docker.LogEvent) *pb.LogEvent {
func logEventToPb(event *container.LogEvent) *pb.LogEvent {
var message *anypb.Any
if event.Message == nil {

View File

@@ -7,7 +7,7 @@ import (
"net/http"
"strings"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
"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 {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
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 {
log.Fatal().Str("filter", r.Header.Get(p.headerFilter)).Msg("Failed to parse container filter")
}

View File

@@ -11,7 +11,7 @@ import (
"os"
"time"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
"github.com/go-chi/jwtauth/v5"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/bcrypt"
@@ -24,7 +24,7 @@ type User struct {
Name string `json:"name" yaml:"name"`
Password string `json:"-" yaml:"password"`
Filter string `json:"-" yaml:"filter"`
ContainerFilter docker.ContainerFilter `json:"-" yaml:"-"`
ContainerFilter container.ContainerFilter `json:"-" yaml:"-"`
}
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))
}
func newUser(username, email, name string, filter docker.ContainerFilter) User {
func newUser(username, email, name string, filter container.ContainerFilter) User {
return User{
Username: username,
Email: email,
@@ -197,9 +197,9 @@ func UserFromContext(ctx context.Context) *User {
}
email := claims["email"].(string)
name := claims["name"].(string)
containerFilter := docker.ContainerFilter{}
containerFilter := container.ContainerFilter{}
if filter, ok := claims["filter"].(string); ok {
containerFilter, err = docker.ParseContainerFilter(filter)
containerFilter, err = container.ParseContainerFilter(filter)
if err != nil {
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 (
"context"

View File

@@ -1,4 +1,4 @@
package docker
package container
import (
"context"
@@ -48,7 +48,7 @@ func TestContainerStore_List(t *testing.T) {
Name: "test",
},
}, 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.Done()
})
@@ -83,7 +83,7 @@ func TestContainerStore_die(t *testing.T) {
},
}, 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) {
ctx := args.Get(0).(context.Context)
events := args.Get(1).(chan<- ContainerEvent)
@@ -98,7 +98,7 @@ func TestContainerStore_die(t *testing.T) {
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{
ID: "1234",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package docker
package container
import (
"fmt"
@@ -7,7 +7,6 @@ import (
"time"
"github.com/amir20/dozzle/internal/utils"
"github.com/docker/docker/api/types/filters"
)
// Container represents an internal representation of docker containers
@@ -70,17 +69,6 @@ func (f ContainerFilter) Exists() bool {
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
const (

View File

@@ -11,10 +11,12 @@ import (
"encoding/json"
"github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/utils"
"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/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/client"
@@ -22,70 +24,41 @@ import (
"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 {
ContainerList(context.Context, container.ListOptions) ([]types.Container, error)
ContainerLogs(context.Context, string, container.LogsOptions) (io.ReadCloser, error)
ContainerList(context.Context, docker.ListOptions) ([]types.Container, error)
ContainerLogs(context.Context, string, docker.LogsOptions) (io.ReadCloser, error)
Events(context.Context, events.ListOptions) (<-chan events.Message, <-chan 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)
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error
ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error
ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error
ContainerStart(ctx context.Context, containerID string, options docker.StartOptions) error
ContainerStop(ctx context.Context, containerID string, options docker.StopOptions) error
ContainerRestart(ctx context.Context, containerID string, options docker.StopOptions) 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 {
cli DockerCLI
host Host
host container.Host
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())
if err != nil {
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.MemTotal = info.MemTotal
host.DockerVersion = info.ServerVersion
host.Swarm = info.Swarm.NodeID != ""
return &httpClient{
cli: cli,
@@ -95,7 +68,7 @@ func NewClient(cli DockerCLI, host Host) Client {
}
// 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())
if err != nil {
@@ -107,16 +80,8 @@ func NewLocalClient(hostname string) (Client, error) {
return nil, err
}
id := info.ID
if info.Swarm.NodeID != "" {
id = info.Swarm.NodeID
}
host := Host{
ID: id,
host := container.Host{
Name: info.Name,
MemTotal: info.MemTotal,
NCPU: info.NCPU,
Endpoint: "local",
Type: "local",
}
@@ -128,7 +93,7 @@ func NewLocalClient(hostname string) (Client, error) {
return NewClient(cli, host), nil
}
func NewRemoteClient(host Host) (Client, error) {
func NewRemoteClient(host container.Host) (container.Client, error) {
if host.URL.Scheme != "tcp" {
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
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")
if json, err := d.cli.ContainerInspect(ctx, id); err == nil {
return newContainerFromJSON(json, d.host.ID), nil
} 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 {
case Start:
return d.cli.ContainerStart(ctx, containerID, container.StartOptions{})
case Stop:
return d.cli.ContainerStop(ctx, containerID, container.StopOptions{})
case Restart:
return d.cli.ContainerRestart(ctx, containerID, container.StopOptions{})
case container.Start:
return d.cli.ContainerStart(ctx, containerID, docker.StartOptions{})
case container.Stop:
return d.cli.ContainerStop(ctx, containerID, docker.StopOptions{})
case container.Restart:
return d.cli.ContainerRestart(ctx, containerID, docker.StopOptions{})
default:
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")
containerListOptions := container.ListOptions{
Filters: filter.asArgs(),
filterArgs := filters.NewArgs()
for key, values := range filter {
for _, value := range values {
filterArgs.Add(key, value)
}
}
containerListOptions := docker.ListOptions{
Filters: filterArgs,
All: true,
}
list, err := d.cli.ContainerList(ctx, containerListOptions)
@@ -192,7 +163,7 @@ func (d *httpClient) ListContainers(ctx context.Context, filter ContainerFilter)
return nil, err
}
var containers = make([]Container, 0, len(list))
var containers = make([]container.Container, 0, len(list))
for _, c := range list {
containers = append(containers, newContainer(c, d.host.ID))
}
@@ -204,7 +175,7 @@ func (d *httpClient) ListContainers(ctx context.Context, filter ContainerFilter)
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)
if err != nil {
@@ -213,7 +184,7 @@ func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<-
defer response.Body.Close()
decoder := json.NewDecoder(response.Body)
var v *container.StatsResponse
var v *docker.StatsResponse
for {
if err := decoder.Decode(&v); err != nil {
return err
@@ -243,7 +214,7 @@ func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<-
select {
case <-ctx.Done():
return nil
case stats <- ContainerStat{
case stats <- container.ContainerStat{
ID: id,
CPUPercent: cpuPercent,
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")
sinceQuery := since.Add(-50 * time.Millisecond).Format(time.RFC3339Nano)
options := container.LogsOptions{
ShowStdout: stdType&STDOUT != 0,
ShowStderr: stdType&STDERR != 0,
options := docker.LogsOptions{
ShowStdout: stdType&container.STDOUT != 0,
ShowStderr: stdType&container.STDERR != 0,
Follow: true,
Tail: strconv.Itoa(100),
Timestamps: true,
@@ -275,7 +246,7 @@ func (d *httpClient) ContainerLogs(ctx context.Context, id string, since time.Ti
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{})
for {
@@ -287,7 +258,7 @@ func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- Contai
case message := <-dockerMessages:
if message.Type == events.ContainerEventType && len(message.Actor.ID) > 0 {
messages <- ContainerEvent{
messages <- container.ContainerEvent{
ActorID: message.Actor.ID[:12],
Name: string(message.Action),
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")
options := container.LogsOptions{
ShowStdout: stdType&STDOUT != 0,
ShowStderr: stdType&STDERR != 0,
options := docker.LogsOptions{
ShowStdout: stdType&container.STDOUT != 0,
ShowStderr: stdType&container.STDERR != 0,
Timestamps: true,
Since: from.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
}
func (d *httpClient) Ping(ctx context.Context) (types.Ping, error) {
return d.cli.Ping(ctx)
func (d *httpClient) Ping(ctx context.Context) error {
_, 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")
return d.host
}
@@ -330,11 +302,7 @@ func (d *httpClient) IsSwarmMode() bool {
return d.info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive
}
func (d *httpClient) SystemInfo() system.Info {
return d.info
}
func newContainer(c types.Container, host string) Container {
func newContainer(c types.Container, host string) container.Container {
name := "no name"
if 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"] != "" {
group = c.Labels["dev.dozzle.group"]
}
return Container{
return container.Container{
ID: c.ID[:12],
Name: name,
Image: c.Image,
@@ -355,12 +323,12 @@ func newContainer(c types.Container, host string) Container {
State: c.State,
Host: host,
Labels: c.Labels,
Stats: utils.NewRingBuffer[ContainerStat](300), // 300 seconds of stats
Stats: utils.NewRingBuffer[container.ContainerStat](300), // 300 seconds of stats
Group: group,
}
}
func newContainerFromJSON(c types.ContainerJSON, host string) Container {
func newContainerFromJSON(c types.ContainerJSON, host string) container.Container {
name := "no name"
if 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"]
}
container := Container{
container := container.Container{
ID: c.ID[:12],
Name: name,
Image: c.Config.Image,
@@ -381,7 +349,7 @@ func newContainerFromJSON(c types.ContainerJSON, host string) Container {
State: c.State.Status,
Host: host,
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,
Tty: c.Config.Tty,
}

View File

@@ -10,8 +10,9 @@ import (
"testing"
"github.com/amir20/dozzle/internal/container"
"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/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -23,7 +24,7 @@ type mockedProxy struct {
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()
containers, ok := args.Get(0).([]types.Container)
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)
reader, ok := args.Get(0).(io.ReadCloser)
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)
}
func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, stream bool) (container.StatsResponseReader, error) {
return container.StatsResponseReader{}, nil
func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, stream bool) (docker.StatsResponseReader, error) {
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)
err := args.Get(0)
@@ -63,7 +64,7 @@ func (m *mockedProxy) ContainerStart(ctx context.Context, containerID string, op
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)
err := args.Get(0)
@@ -74,7 +75,7 @@ func (m *mockedProxy) ContainerStop(ctx context.Context, containerID string, opt
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)
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) {
proxy := new(mockedProxy)
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")
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) {
proxy := new(mockedProxy)
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")
require.Error(t, err, "test.")
@@ -124,9 +125,9 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
proxy := new(mockedProxy)
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.")
Ids := []string{"1234567890_a", "abcdefghijkl"}
@@ -149,7 +150,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
reader := io.NopCloser(bytes.NewReader(b))
since := time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)
options := container.LogsOptions{
options := docker.LogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,
@@ -158,8 +159,8 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
Since: "2020-12-31T23:59:59.95Z"}
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}}
logReader, _ := client.ContainerLogs(context.Background(), id, since, STDALL)
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
logReader, _ := client.ContainerLogs(context.Background(), id, since, container.STDALL)
actual, _ := io.ReadAll(logReader)
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"))
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.Error(t, err, "error should have been returned")
@@ -185,10 +186,10 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
proxy := new(mockedProxy)
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)
client := &httpClient{proxy, Host{ID: "localhost"}, system.Info{}}
client := &httpClient{proxy, container.Host{ID: "localhost"}, system.Info{}}
container, err := client.FindContainer(context.Background(), "abcdefghijkl")
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) {
proxy := new(mockedProxy)
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")
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) {
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)}
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("ContainerStart", 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)
container, err := client.FindContainer(context.Background(), "abcdefghijkl")
c, err := client.FindContainer(context.Background(), "abcdefghijkl")
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"}
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")
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) {
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("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("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")
actions := []string{"start", "stop", "restart"}
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")
assert.Error(t, err, "error should have been returned")
}

View File

@@ -5,7 +5,7 @@ import (
"crypto/tls"
"github.com/amir20/dozzle/internal/agent"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
"github.com/rs/zerolog/log"
)
@@ -14,7 +14,7 @@ func RPCRequest(ctx context.Context, addr string, certs tls.Certificate) error {
if err != nil {
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")
return err
}

View File

@@ -2,12 +2,12 @@ package cli
import (
"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/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 {
return
}
@@ -29,7 +29,7 @@ func StartEvent(args Args, mode string, client docker.Client, subCommand string)
host := client.Host()
event.ServerID = host.ID
event.ServerVersion = host.DockerVersion
event.IsSwarmMode = client.SystemInfo().Swarm.NodeID != ""
event.IsSwarmMode = host.Swarm
} else {
event.ServerID = "n/a"
}

View File

@@ -4,19 +4,20 @@ import (
"context"
"embed"
"github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/docker"
docker_support "github.com/amir20/dozzle/internal/support/docker"
"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
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`)
}
for _, remoteHost := range args.RemoteHost {
host, err := docker.ParseConnection(remoteHost)
host, err := container.ParseConnection(remoteHost)
if err != nil {
log.Fatal().Err(err).Interface("host", remoteHost).Msg("Could not parse remote host")
}

View File

@@ -7,13 +7,13 @@ import (
"time"
"github.com/amir20/dozzle/internal/agent"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
"github.com/rs/zerolog/log"
)
type agentService struct {
client *agent.Client
host docker.Host
host container.Host
}
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)
}
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)
}
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)
}
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)
}
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")
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)
if err != nil {
host := a.host
@@ -55,18 +55,18 @@ func (a *agentService) Host(ctx context.Context) (docker.Host, error) {
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)
}
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)
}
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)
}
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)
}

View File

@@ -5,59 +5,59 @@ import (
"io"
"time"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
)
type ClientService interface {
FindContainer(ctx context.Context, id string, filter docker.ContainerFilter) (docker.Container, error)
ListContainers(ctx context.Context, filter docker.ContainerFilter) ([]docker.Container, error)
Host(ctx context.Context) (docker.Host, error)
ContainerAction(ctx context.Context, container docker.Container, action docker.ContainerAction) error
LogsBetweenDates(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (<-chan *docker.LogEvent, error)
RawLogs(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (io.ReadCloser, error)
FindContainer(ctx context.Context, id string, filter container.ContainerFilter) (container.Container, error)
ListContainers(ctx context.Context, filter container.ContainerFilter) ([]container.Container, error)
Host(ctx context.Context) (container.Host, error)
ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) 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 container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error)
// Subscriptions
SubscribeStats(ctx context.Context, stats chan<- docker.ContainerStat)
SubscribeEvents(ctx context.Context, events chan<- docker.ContainerEvent)
SubscribeContainersStarted(ctx context.Context, containers chan<- docker.Container)
SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat)
SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent)
SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container)
// 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 {
client docker.Client
store *docker.ContainerStore
client container.Client
store *container.ContainerStore
}
func NewDockerClientService(client docker.Client, filter docker.ContainerFilter) ClientService {
func NewDockerClientService(client container.Client, filter container.ContainerFilter) ClientService {
return &dockerClientService{
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)
}
func (d *dockerClientService) LogsBetweenDates(ctx context.Context, container docker.Container, from time.Time, to time.Time, stdTypes docker.StdType) (<-chan *docker.LogEvent, error) {
reader, err := d.client.ContainerLogsBetweenDates(ctx, container.ID, from, to, stdTypes)
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, c.ID, from, to, stdTypes)
if err != nil {
return nil, err
}
g := docker.NewEventGenerator(ctx, reader, container)
g := container.NewEventGenerator(ctx, reader, c)
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 {
reader, err := d.client.ContainerLogs(ctx, container.ID, from, stdTypes)
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, c.ID, from, stdTypes)
if err != nil {
return err
}
g := docker.NewEventGenerator(ctx, reader, container)
g := container.NewEventGenerator(ctx, reader, c)
for event := range g.Events {
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)
}
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)
}
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)
}
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
}
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)
}
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)
}
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)
}

View File

@@ -5,26 +5,26 @@ import (
"io"
"time"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
)
type containerService struct {
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)
}
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)
}
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)
}
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)
}

View File

@@ -5,14 +5,14 @@ import (
"fmt"
"time"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
"github.com/rs/zerolog/log"
)
type ContainerFilter = func(*docker.Container) bool
type ContainerFilter = func(*container.Container) bool
type HostUnavailableError struct {
Host docker.Host
Host container.Host
Err error
}
@@ -24,9 +24,9 @@ type ClientManager interface {
Find(id string) (ClientService, bool)
List() []ClientService
RetryAndList() ([]ClientService, []error)
Subscribe(ctx context.Context, channel chan<- docker.Host)
Hosts(ctx context.Context) []docker.Host
LocalClients() []docker.Client
Subscribe(ctx context.Context, channel chan<- container.Host)
Hosts(ctx context.Context) []container.Host
LocalClients() []container.Client
}
type MultiHostService struct {
@@ -43,7 +43,7 @@ func NewMultiHostService(manager ClientManager, timeout time.Duration) *MultiHos
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)
if !ok {
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
}
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)
if !ok {
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)
}
func (m *MultiHostService) ListAllContainers(filter docker.ContainerFilter) ([]docker.Container, []error) {
containers := make([]docker.Container, 0)
func (m *MultiHostService) ListAllContainers(filter container.ContainerFilter) ([]container.Container, []error) {
containers := make([]container.Container, 0)
clients, errors := m.manager.RetryAndList()
for _, client := range clients {
@@ -94,9 +94,9 @@ func (m *MultiHostService) ListAllContainers(filter docker.ContainerFilter) ([]d
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)
filtered := make([]docker.Container, 0, len(containers))
filtered := make([]container.Container, 0, len(containers))
for _, container := range containers {
if filter(&container) {
filtered = append(filtered, container)
@@ -105,15 +105,15 @@ func (m *MultiHostService) ListAllContainersFiltered(userFilter docker.Container
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() {
client.SubscribeEvents(ctx, events)
client.SubscribeStats(ctx, stats)
}
}
func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, containers chan<- docker.Container, filter ContainerFilter) {
newContainers := make(chan docker.Container)
func (m *MultiHostService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container, filter ContainerFilter) {
newContainers := make(chan container.Container)
for _, client := range m.manager.List() {
client.SubscribeContainersStarted(ctx, newContainers)
}
@@ -139,25 +139,25 @@ func (m *MultiHostService) TotalClients() int {
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)
defer cancel()
return m.manager.Hosts(ctx)
}
func (m *MultiHostService) LocalHost() (docker.Host, error) {
func (m *MultiHostService) LocalHost() (container.Host, error) {
for _, host := range m.Hosts() {
if host.Type == "local" {
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)
}
func (m *MultiHostService) LocalClients() []docker.Client {
func (m *MultiHostService) LocalClients() []container.Client {
return m.manager.LocalClients()
}

View File

@@ -8,7 +8,7 @@ import (
"time"
"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/samber/lo"
lop "github.com/samber/lo/parallel"
@@ -21,7 +21,7 @@ type RetriableClientManager struct {
failedAgents []string
certs tls.Certificate
mu sync.RWMutex
subscribers *xsync.MapOf[context.Context, chan<- docker.Host]
subscribers *xsync.MapOf[context.Context, chan<- container.Host]
timeout time.Duration
}
@@ -72,12 +72,12 @@ func NewRetriableClientManager(agents []string, timeout time.Duration, certs tls
clients: clientMap,
failedAgents: failed,
certs: certs,
subscribers: xsync.NewMapOf[context.Context, chan<- docker.Host](),
subscribers: xsync.NewMapOf[context.Context, chan<- container.Host](),
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)
go func() {
@@ -111,11 +111,11 @@ func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) {
}
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
// We don't want to block the subscribers in event.go
go func(host docker.Host) {
go func(host container.Host) {
select {
case channel <- host:
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))
}
func (m *RetriableClientManager) Hosts(ctx context.Context) []docker.Host {
func (m *RetriableClientManager) Hosts(ctx context.Context) []container.Host {
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)
if err != nil {
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 {
hosts = append(hosts, docker.Host{
hosts = append(hosts, container.Host{
ID: endpoint,
Name: endpoint,
Endpoint: endpoint,
@@ -180,10 +180,10 @@ func (m *RetriableClientManager) Hosts(ctx context.Context) []docker.Host {
return hosts
}
func (m *RetriableClientManager) LocalClients() []docker.Client {
func (m *RetriableClientManager) LocalClients() []container.Client {
services := m.List()
clients := make([]docker.Client, 0)
clients := make([]container.Client, 0)
for _, service := range services {
if clientService, ok := service.(*dockerClientService); ok {

View File

@@ -10,7 +10,7 @@ import (
"time"
"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/samber/lo"
lop "github.com/samber/lo/parallel"
@@ -22,8 +22,8 @@ type SwarmClientManager struct {
clients map[string]ClientService
certs tls.Certificate
mu sync.RWMutex
subscribers *xsync.MapOf[context.Context, chan<- docker.Host]
localClient docker.Client
subscribers *xsync.MapOf[context.Context, chan<- container.Host]
localClient container.Client
localIPs []string
name string
timeout time.Duration
@@ -47,7 +47,7 @@ func localIPs() []string {
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)
localService := NewDockerClientService(localClient, filter)
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)
defer cancel()
container, err := localClient.FindContainer(ctx, id)
c, err := localClient.FindContainer(ctx, id)
if err != nil {
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")
@@ -72,7 +72,7 @@ func NewSwarmClientManager(localClient docker.Client, certs tls.Certificate, tim
localClient: localClient,
clients: clientMap,
certs: certs,
subscribers: xsync.NewMapOf[context.Context, chan<- docker.Host](),
subscribers: xsync.NewMapOf[context.Context, chan<- container.Host](),
localIPs: localIPs(),
name: serviceName,
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.agentManager.Subscribe(ctx, channel)
@@ -159,7 +159,7 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) {
m.clients[host.ID] = client
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.Type = "swarm"
@@ -205,12 +205,12 @@ func (m *SwarmClientManager) Find(id string) (ClientService, bool) {
return client, ok
}
func (m *SwarmClientManager) Hosts(ctx context.Context) []docker.Host {
func (m *SwarmClientManager) Hosts(ctx context.Context) []container.Host {
m.mu.RLock()
clients := lo.Values(m.clients)
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)
if err != nil {
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))
}
func (m *SwarmClientManager) LocalClients() []docker.Client {
return []docker.Client{m.localClient}
func (m *SwarmClientManager) LocalClients() []container.Client {
return []container.Client{m.localClient}
}

View File

@@ -5,7 +5,7 @@ import (
"regexp"
"strings"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
"github.com/rs/zerolog/log"
orderedmap "github.com/wk8/go-ordered-map/v2"
)
@@ -26,7 +26,7 @@ func ParseRegex(search string) (*regexp.Regexp, error) {
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) {
case string:
if re.MatchString(value) {

View File

@@ -6,7 +6,7 @@ import (
"time"
"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/rs/zerolog/log"
)
@@ -30,7 +30,7 @@ func (h *handler) containerActions(w http.ResponseWriter, r *http.Request) {
return
}
parsedAction, err := docker.ParseContainerAction(action)
parsedAction, err := container.ParseContainerAction(action)
if err != nil {
log.Error().Err(err).Msg("error while trying to parse action")
http.Error(w, err.Error(), http.StatusBadRequest)

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ import (
"net/http/httptest"
"testing"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
@@ -21,17 +21,17 @@ func Test_handler_download_logs(t *testing.T) {
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("ContainerLogsBetweenDates", mock.Anything, id, mock.Anything, mock.Anything, docker.STDOUT).Return(io.NopCloser(bytes.NewReader(data)), nil)
mockedClient.On("Host").Return(docker.Host{
mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Tty: false}, nil)
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, mock.Anything, mock.Anything, container.STDOUT).Return(io.NopCloser(bytes.NewReader(data)), nil)
mockedClient.On("Host").Return(container.Host{
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)
})
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"},
}, nil)

View File

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

View File

@@ -9,7 +9,7 @@ import (
"net/http/httptest"
"testing"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
docker_support "github.com/amir20/dozzle/internal/support/docker"
"github.com/amir20/dozzle/internal/utils"
"github.com/beme/abide"
@@ -24,17 +24,17 @@ func Test_handler_streamEvents_happy(t *testing.T) {
mockedClient := new(MockedClient)
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{}, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
messages := args.Get(1).(chan<- docker.ContainerEvent)
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{}, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
messages := args.Get(1).(chan<- container.ContainerEvent)
time.Sleep(50 * time.Millisecond)
messages <- docker.ContainerEvent{
messages <- container.ContainerEvent{
Name: "start",
ActorID: "1234",
Host: "localhost",
}
messages <- docker.ContainerEvent{
messages <- container.ContainerEvent{
Name: "something-random",
ActorID: "1234",
Host: "localhost",
@@ -42,19 +42,19 @@ func Test_handler_streamEvents_happy(t *testing.T) {
time.Sleep(50 * time.Millisecond)
cancel()
})
mockedClient.On("FindContainer", mock.Anything, "1234").Return(docker.Container{
mockedClient.On("FindContainer", mock.Anything, "1234").Return(container.Container{
ID: "1234",
Name: "test",
Image: "test",
Stats: utils.NewRingBuffer[docker.ContainerStat](300), // 300 seconds of stats
Stats: utils.NewRingBuffer[container.ContainerStat](300), // 300 seconds of stats
}, nil)
mockedClient.On("Host").Return(docker.Host{
mockedClient.On("Host").Return(container.Host{
ID: "localhost",
})
// 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)
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()
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")
w.WriteHeader(http.StatusInternalServerError)
}

View File

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

View File

@@ -14,7 +14,7 @@ import (
"strings"
"testing"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
"github.com/beme/abide"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
@@ -36,25 +36,25 @@ func Test_handler_streamLogs_happy(t *testing.T) {
mockedClient := new(MockedClient)
data := makeMessage("INFO Testing logs...", docker.STDOUT)
data := makeMessage("INFO Testing logs...", container.STDOUT)
now := time.Now()
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.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("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Tty: false, Host: "localhost", StartedAt: now}, nil)
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, now, container.STDALL).Return(io.NopCloser(bytes.NewReader(data)), nil).
Run(func(args mock.Arguments) {
go func() {
time.Sleep(50 * time.Millisecond)
cancel()
}()
})
mockedClient.On("Host").Return(docker.Host{
mockedClient.On("Host").Return(container.Host{
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"},
}, 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)
})
@@ -80,27 +80,27 @@ func Test_handler_streamLogs_happy_with_id(t *testing.T) {
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)
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.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("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Host: "localhost", StartedAt: started}, nil)
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, started, container.STDALL).Return(io.NopCloser(bytes.NewReader(data)), nil).
Run(func(args mock.Arguments) {
go func() {
time.Sleep(50 * time.Millisecond)
cancel()
}()
})
mockedClient.On("Host").Return(docker.Host{
mockedClient.On("Host").Return(container.Host{
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"},
}, 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)
})
@@ -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)
mockedClient := new(MockedClient)
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.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("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Host: "localhost", StartedAt: started}, nil)
mockedClient.On("ContainerLogs", mock.Anything, id, started, container.STDALL).Return(io.NopCloser(strings.NewReader("")), io.EOF).
Run(func(args mock.Arguments) {
go func() {
time.Sleep(50 * time.Millisecond)
cancel()
}()
})
mockedClient.On("Host").Return(docker.Host{
mockedClient.On("Host").Return(container.Host{
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"},
}, 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)
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)
mockedClient := new(MockedClient)
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.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("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Host: "localhost", StartedAt: started}, nil)
mockedClient.On("ContainerLogs", mock.Anything, id, started, container.STDALL).Return(io.NopCloser(strings.NewReader("")), errors.New("test error")).
Run(func(args mock.Arguments) {
go func() {
time.Sleep(50 * time.Millisecond)
cancel()
}()
})
mockedClient.On("Host").Return(docker.Host{
mockedClient.On("Host").Return(container.Host{
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"},
}, 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)
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.")
mockedClient := new(MockedClient)
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.Container{ID: id, Host: "localhost"}, nil)
mockedClient.On("Host").Return(docker.Host{
mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Host: "localhost"}, nil)
mockedClient.On("Host").Return(container.Host{
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"},
}, 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) {
time.Sleep(50 * time.Millisecond)
})
@@ -231,19 +231,19 @@ func Test_handler_between_dates(t *testing.T) {
mockedClient := new(MockedClient)
first := makeMessage("2020-05-13T18:55:37.772853839Z INFO Testing stdout logs...\n", docker.STDOUT)
second := makeMessage("2020-05-13T18:56:37.772853839Z INFO Testing stderr logs...\n", docker.STDERR)
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", container.STDERR)
data := append(first, second...)
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, from, to, docker.STDALL).Return(io.NopCloser(bytes.NewReader(data)), nil)
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.Container{ID: id}, nil)
mockedClient.On("Host").Return(docker.Host{
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, from, to, container.STDALL).Return(io.NopCloser(bytes.NewReader(data)), nil)
mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id}, nil)
mockedClient.On("Host").Return(container.Host{
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"},
}, 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)
rr := httptest.NewRecorder()
@@ -273,24 +273,24 @@ func Test_handler_between_dates_with_fill(t *testing.T) {
mockedClient := new(MockedClient)
first := makeMessage("2020-05-13T18:55:37.772853839Z INFO Testing stdout logs...\n", docker.STDOUT)
second := makeMessage("2020-05-13T18:56:37.772853839Z INFO Testing stderr logs...\n", docker.STDERR)
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", container.STDERR)
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).
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).
Once()
mockedClient.On("FindContainer", mock.Anything, id).Return(docker.Container{ID: id}, nil)
mockedClient.On("Host").Return(docker.Host{
mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id}, nil)
mockedClient.On("Host").Return(container.Host{
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"},
}, 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)
rr := httptest.NewRecorder()
@@ -300,7 +300,7 @@ func Test_handler_between_dates_with_fill(t *testing.T) {
mockedClient.AssertExpectations(t)
}
func makeMessage(message string, stream docker.StdType) []byte {
func makeMessage(message string, stream container.StdType) []byte {
data := make([]byte, 8)
binary.BigEndian.PutUint32(data[4:], uint32(len(message)))
data[0] = byte(stream / 2)

View File

@@ -8,7 +8,7 @@ import (
"strings"
"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"
"github.com/go-chi/chi/v5"
@@ -34,7 +34,7 @@ type Config struct {
Dev bool
Authorization Authorization
EnableActions bool
Filter docker.ContainerFilter
Filter container.ContainerFilter
}
type Authorization struct {

View File

@@ -8,7 +8,7 @@ import (
"io"
"io/fs"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/container"
docker_support "github.com/amir20/dozzle/internal/support/docker"
"github.com/docker/docker/api/types/system"
"github.com/go-chi/chi/v5"
@@ -20,46 +20,46 @@ import (
type MockedClient struct {
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)
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)
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)
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)
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)
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
}
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)
return args.Get(0).(io.ReadCloser), args.Error(1)
}
func (m *MockedClient) Host() docker.Host {
func (m *MockedClient) Host() container.Host {
args := m.Called()
return args.Get(0).(docker.Host)
return args.Get(0).(container.Host)
}
func (m *MockedClient) IsSwarmMode() bool {
@@ -70,14 +70,14 @@ func (m *MockedClient) SystemInfo() system.Info {
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 {
client = new(MockedClient)
client.(*MockedClient).On("ListContainers", mock.Anything, mock.Anything).Return([]docker.Container{}, nil)
client.(*MockedClient).On("Host").Return(docker.Host{
client.(*MockedClient).On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{}, nil)
client.(*MockedClient).On("Host").Return(container.Host{
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 {
@@ -86,7 +86,7 @@ func createHandler(client docker.Client, content fs.FS, config Config) *chi.Mux
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)
return createRouter(&handler{
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}})
}

View File

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