mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-21 13:23:07 +01:00
chore(refactor): refactors docker client for better testing (#2302)
* chore(refactor): refactors docker client for better testing * more refactoring and clenaing up tests
This commit is contained in:
@@ -20,12 +20,6 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type dockerClient struct {
|
||||
cli dockerProxy
|
||||
filters filters.Args
|
||||
host *Host
|
||||
}
|
||||
|
||||
type StdType int
|
||||
|
||||
const (
|
||||
@@ -48,7 +42,7 @@ func (s StdType) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
type dockerProxy interface {
|
||||
type DockerCLI interface {
|
||||
ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error)
|
||||
ContainerLogs(context.Context, string, types.ContainerLogsOptions) (io.ReadCloser, error)
|
||||
Events(context.Context, types.EventsOptions) (<-chan events.Message, <-chan error)
|
||||
@@ -57,20 +51,18 @@ type dockerProxy interface {
|
||||
Ping(ctx context.Context) (types.Ping, error)
|
||||
}
|
||||
|
||||
// Client is a proxy around the docker client
|
||||
type Client interface {
|
||||
ListContainers() ([]Container, error)
|
||||
FindContainer(string) (Container, error)
|
||||
ContainerLogs(context.Context, string, string, StdType) (io.ReadCloser, error)
|
||||
Events(context.Context, chan<- ContainerEvent) <-chan 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
|
||||
type Client struct {
|
||||
cli DockerCLI
|
||||
filters filters.Args
|
||||
host *Host
|
||||
}
|
||||
|
||||
func NewClient(cli DockerCLI, filters filters.Args, host *Host) *Client {
|
||||
return &Client{cli, filters, host}
|
||||
}
|
||||
|
||||
// NewClientWithFilters creates a new instance of Client with docker filters
|
||||
func NewClientWithFilters(f map[string][]string) (Client, error) {
|
||||
func NewClientWithFilters(f map[string][]string) (*Client, error) {
|
||||
filterArgs := filters.NewArgs()
|
||||
for key, values := range f {
|
||||
for _, value := range values {
|
||||
@@ -86,10 +78,10 @@ func NewClientWithFilters(f map[string][]string) (Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dockerClient{cli, filterArgs, &Host{Name: "localhost", ID: "localhost"}}, nil
|
||||
return NewClient(cli, filterArgs, &Host{Name: "localhost", ID: "localhost"}), nil
|
||||
}
|
||||
|
||||
func NewClientWithTlsAndFilter(f map[string][]string, host Host) (Client, error) {
|
||||
func NewClientWithTlsAndFilter(f map[string][]string, host Host) (*Client, error) {
|
||||
filterArgs := filters.NewArgs()
|
||||
for key, values := range f {
|
||||
for _, value := range values {
|
||||
@@ -122,10 +114,10 @@ func NewClientWithTlsAndFilter(f map[string][]string, host Host) (Client, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dockerClient{cli, filterArgs, &host}, nil
|
||||
return NewClient(cli, filterArgs, &host), nil
|
||||
}
|
||||
|
||||
func (d *dockerClient) FindContainer(id string) (Container, error) {
|
||||
func (d *Client) FindContainer(id string) (Container, error) {
|
||||
var container Container
|
||||
containers, err := d.ListContainers()
|
||||
if err != nil {
|
||||
@@ -153,7 +145,7 @@ func (d *dockerClient) FindContainer(id string) (Container, error) {
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (d *dockerClient) ListContainers() ([]Container, error) {
|
||||
func (d *Client) ListContainers() ([]Container, error) {
|
||||
containerListOptions := types.ContainerListOptions{
|
||||
Filters: d.filters,
|
||||
All: true,
|
||||
@@ -188,7 +180,7 @@ func (d *dockerClient) ListContainers() ([]Container, error) {
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (d *dockerClient) ContainerStats(ctx context.Context, id string, stats chan<- ContainerStat) error {
|
||||
func (d *Client) ContainerStats(ctx context.Context, id string, stats chan<- ContainerStat) error {
|
||||
response, err := d.cli.ContainerStats(ctx, id, true)
|
||||
|
||||
if err != nil {
|
||||
@@ -240,7 +232,7 @@ func (d *dockerClient) ContainerStats(ctx context.Context, id string, stats chan
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dockerClient) ContainerLogs(ctx context.Context, id string, since string, stdType StdType) (io.ReadCloser, error) {
|
||||
func (d *Client) ContainerLogs(ctx context.Context, id string, since string, stdType StdType) (io.ReadCloser, error) {
|
||||
log.WithField("id", id).WithField("since", since).WithField("stdType", stdType).Debug("streaming logs for container")
|
||||
|
||||
if since != "" {
|
||||
@@ -268,7 +260,7 @@ func (d *dockerClient) ContainerLogs(ctx context.Context, id string, since strin
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
func (d *dockerClient) Events(ctx context.Context, messages chan<- ContainerEvent) <-chan error {
|
||||
func (d *Client) Events(ctx context.Context, messages chan<- ContainerEvent) <-chan error {
|
||||
dockerMessages, errors := d.cli.Events(ctx, types.EventsOptions{})
|
||||
|
||||
go func() {
|
||||
@@ -296,7 +288,7 @@ func (d *dockerClient) Events(ctx context.Context, messages chan<- ContainerEven
|
||||
return errors
|
||||
}
|
||||
|
||||
func (d *dockerClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType StdType) (io.ReadCloser, error) {
|
||||
func (d *Client) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType StdType) (io.ReadCloser, error) {
|
||||
options := types.ContainerLogsOptions{
|
||||
ShowStdout: stdType&STDOUT != 0,
|
||||
ShowStderr: stdType&STDERR != 0,
|
||||
@@ -315,11 +307,11 @@ func (d *dockerClient) ContainerLogsBetweenDates(ctx context.Context, id string,
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
func (d *dockerClient) Ping(ctx context.Context) (types.Ping, error) {
|
||||
func (d *Client) Ping(ctx context.Context) (types.Ping, error) {
|
||||
return d.cli.Ping(ctx)
|
||||
}
|
||||
|
||||
func (d *dockerClient) Host() *Host {
|
||||
func (d *Client) Host() *Host {
|
||||
return d.host
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
|
||||
type mockedProxy struct {
|
||||
mock.Mock
|
||||
dockerProxy
|
||||
DockerCLI
|
||||
}
|
||||
|
||||
func (m *mockedProxy) ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
||||
@@ -53,7 +53,7 @@ func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, st
|
||||
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
||||
proxy := new(mockedProxy)
|
||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
||||
client := &dockerClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
|
||||
list, err := client.ListContainers()
|
||||
assert.Empty(t, list, "list should be empty")
|
||||
@@ -65,7 +65,7 @@ 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 := &dockerClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
|
||||
list, err := client.ListContainers()
|
||||
assert.Nil(t, list, "list should be nil")
|
||||
@@ -88,7 +88,7 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
|
||||
|
||||
proxy := new(mockedProxy)
|
||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
||||
client := &dockerClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
|
||||
list, err := client.ListContainers()
|
||||
require.NoError(t, err, "error should not return an error.")
|
||||
@@ -125,7 +125,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
|
||||
options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "300", Timestamps: true, Since: "since"}
|
||||
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
|
||||
|
||||
client := &dockerClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
logReader, _ := client.ContainerLogs(context.Background(), id, "since", STDALL)
|
||||
|
||||
actual, _ := io.ReadAll(logReader)
|
||||
@@ -139,7 +139,7 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
|
||||
|
||||
proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
|
||||
|
||||
client := &dockerClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
|
||||
reader, err := client.ContainerLogs(context.Background(), id, "", STDALL)
|
||||
|
||||
@@ -166,7 +166,7 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
|
||||
json := types.ContainerJSON{Config: &container.Config{Tty: false}}
|
||||
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
|
||||
|
||||
client := &dockerClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
|
||||
container, err := client.FindContainer("abcdefghijkl")
|
||||
require.NoError(t, err, "error should not be thrown")
|
||||
@@ -195,7 +195,7 @@ func Test_dockerClient_FindContainer_error(t *testing.T) {
|
||||
|
||||
proxy := new(mockedProxy)
|
||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
||||
client := &dockerClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
|
||||
_, err := client.FindContainer("not_valid")
|
||||
require.Error(t, err, "error should be thrown")
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -197,38 +196,3 @@ func checkPosition(currentEvent *LogEvent, nextEvent *LogEvent) {
|
||||
currentEvent.Position = END
|
||||
}
|
||||
}
|
||||
|
||||
var KEY_VALUE_REGEX = regexp.MustCompile(`level=(\w+)`)
|
||||
var ANSI_COLOR_REGEX = regexp.MustCompile(`\x1b\[[0-9;]*m`)
|
||||
|
||||
func guessLogLevel(logEvent *LogEvent) string {
|
||||
switch value := logEvent.Message.(type) {
|
||||
case string:
|
||||
levels := []string{"error", "warn", "warning", "info", "debug", "trace", "fatal"}
|
||||
stripped := ANSI_COLOR_REGEX.ReplaceAllString(value, "") // remove ansi color codes
|
||||
for _, level := range levels {
|
||||
if match, _ := regexp.MatchString("(?i)^"+level+"[^a-z]", stripped); match {
|
||||
return level
|
||||
}
|
||||
|
||||
if strings.Contains(value, "["+strings.ToUpper(level)+"]") {
|
||||
return level
|
||||
}
|
||||
|
||||
if strings.Contains(value, " "+strings.ToUpper(level)+" ") {
|
||||
return level
|
||||
}
|
||||
}
|
||||
|
||||
if matches := KEY_VALUE_REGEX.FindStringSubmatch(value); matches != nil {
|
||||
return matches[1]
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
if level, ok := value["level"].(string); ok {
|
||||
return level
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
41
docker/level_guesser.go
Normal file
41
docker/level_guesser.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var KEY_VALUE_REGEX = regexp.MustCompile(`level=(\w+)`)
|
||||
var ANSI_COLOR_REGEX = regexp.MustCompile(`\x1b\[[0-9;]*m`)
|
||||
|
||||
func guessLogLevel(logEvent *LogEvent) string {
|
||||
switch value := logEvent.Message.(type) {
|
||||
case string:
|
||||
levels := []string{"error", "warn", "warning", "info", "debug", "trace", "fatal"}
|
||||
stripped := ANSI_COLOR_REGEX.ReplaceAllString(value, "") // remove ansi color codes
|
||||
for _, level := range levels {
|
||||
if match, _ := regexp.MatchString("(?i)^"+level+"[^a-z]", stripped); match {
|
||||
return level
|
||||
}
|
||||
|
||||
if strings.Contains(value, "["+strings.ToUpper(level)+"]") {
|
||||
return level
|
||||
}
|
||||
|
||||
if strings.Contains(value, " "+strings.ToUpper(level)+" ") {
|
||||
return level
|
||||
}
|
||||
}
|
||||
|
||||
if matches := KEY_VALUE_REGEX.FindStringSubmatch(value); matches != nil {
|
||||
return matches[1]
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
if level, ok := value["level"].(string); ok {
|
||||
return level
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
10
main.go
10
main.go
@@ -137,8 +137,10 @@ func doStartEvent(arg args) {
|
||||
}
|
||||
}
|
||||
|
||||
func createClients(args args, localClientFactory func(map[string][]string) (docker.Client, error), remoteClientFactory func(map[string][]string, docker.Host) (docker.Client, error)) map[string]docker.Client {
|
||||
clients := make(map[string]docker.Client)
|
||||
func createClients(args args,
|
||||
localClientFactory func(map[string][]string) (*docker.Client, error),
|
||||
remoteClientFactory func(map[string][]string, docker.Host) (*docker.Client, error)) map[string]web.DockerClient {
|
||||
clients := make(map[string]web.DockerClient)
|
||||
|
||||
if localClient := createLocalClient(args, localClientFactory); localClient != nil {
|
||||
clients[localClient.Host().ID] = localClient
|
||||
@@ -166,7 +168,7 @@ func createClients(args args, localClientFactory func(map[string][]string) (dock
|
||||
return clients
|
||||
}
|
||||
|
||||
func createServer(args args, clients map[string]docker.Client) *http.Server {
|
||||
func createServer(args args, clients map[string]web.DockerClient) *http.Server {
|
||||
_, dev := os.LookupEnv("DEV")
|
||||
config := web.Config{
|
||||
Addr: args.Addr,
|
||||
@@ -206,7 +208,7 @@ func createServer(args args, clients map[string]docker.Client) *http.Server {
|
||||
return web.CreateServer(clients, assets, config)
|
||||
}
|
||||
|
||||
func createLocalClient(args args, localClientFactory func(map[string][]string) (docker.Client, error)) docker.Client {
|
||||
func createLocalClient(args args, localClientFactory func(map[string][]string) (*docker.Client, error)) *docker.Client {
|
||||
for i := 1; ; i++ {
|
||||
dockerClient, err := localClientFactory(args.Filter)
|
||||
if err == nil {
|
||||
|
||||
115
main_test.go
115
main_test.go
@@ -1,37 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/amir20/dozzle/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type fakeClient struct {
|
||||
docker.Client
|
||||
type fakeCLI struct {
|
||||
docker.DockerCLI
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (f *fakeClient) ListContainers() ([]docker.Container, error) {
|
||||
func (f *fakeCLI) ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
||||
args := f.Called()
|
||||
return args.Get(0).([]docker.Container), args.Error(1)
|
||||
}
|
||||
|
||||
func (f *fakeClient) Host() *docker.Host {
|
||||
args := f.Called()
|
||||
return args.Get(0).(*docker.Host)
|
||||
return args.Get(0).([]types.Container), args.Error(1)
|
||||
}
|
||||
|
||||
func Test_valid_localhost(t *testing.T) {
|
||||
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
||||
client := new(fakeClient)
|
||||
client.On("ListContainers").Return([]docker.Container{}, nil)
|
||||
client.On("Host").Return(&docker.Host{
|
||||
client := new(fakeCLI)
|
||||
client.On("ContainerList").Return([]types.Container{}, nil)
|
||||
fakeClientFactory := func(filter map[string][]string) (*docker.Client, error) {
|
||||
return docker.NewClient(client, filters.NewArgs(), &docker.Host{
|
||||
ID: "localhost",
|
||||
})
|
||||
return client, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
args := args{}
|
||||
@@ -39,16 +36,16 @@ func Test_valid_localhost(t *testing.T) {
|
||||
actualClient := createLocalClient(args, fakeClientFactory)
|
||||
|
||||
assert.NotNil(t, actualClient)
|
||||
client.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_invalid_localhost(t *testing.T) {
|
||||
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
||||
client := new(fakeClient)
|
||||
client.On("ListContainers").Return([]docker.Container{}, errors.New("error"))
|
||||
client.On("Host").Return(&docker.Host{
|
||||
client := new(fakeCLI)
|
||||
client.On("ContainerList").Return([]types.Container{}, errors.New("error"))
|
||||
fakeClientFactory := func(filter map[string][]string) (*docker.Client, error) {
|
||||
return docker.NewClient(client, filters.NewArgs(), &docker.Host{
|
||||
ID: "localhost",
|
||||
})
|
||||
return client, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
args := args{}
|
||||
@@ -56,26 +53,24 @@ func Test_invalid_localhost(t *testing.T) {
|
||||
actualClient := createLocalClient(args, fakeClientFactory)
|
||||
|
||||
assert.Nil(t, actualClient)
|
||||
client.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_valid_remote(t *testing.T) {
|
||||
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
||||
client := new(fakeClient)
|
||||
client.On("ListContainers").Return([]docker.Container{}, errors.New("error"))
|
||||
client.On("Host").Return(&docker.Host{
|
||||
local := new(fakeCLI)
|
||||
local.On("ContainerList").Return([]types.Container{}, errors.New("error"))
|
||||
fakeLocalClientFactory := func(filter map[string][]string) (*docker.Client, error) {
|
||||
return docker.NewClient(local, filters.NewArgs(), &docker.Host{
|
||||
ID: "localhost",
|
||||
})
|
||||
|
||||
return client, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
|
||||
client := new(fakeClient)
|
||||
client.On("ListContainers").Return([]docker.Container{}, nil)
|
||||
client.On("Host").Return(&docker.Host{
|
||||
remote := new(fakeCLI)
|
||||
remote.On("ContainerList").Return([]types.Container{}, nil)
|
||||
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (*docker.Client, error) {
|
||||
return docker.NewClient(remote, filters.NewArgs(), &docker.Host{
|
||||
ID: "test",
|
||||
})
|
||||
return client, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
args := args{
|
||||
@@ -87,27 +82,26 @@ func Test_valid_remote(t *testing.T) {
|
||||
assert.Equal(t, 1, len(clients))
|
||||
assert.Contains(t, clients, "test")
|
||||
assert.NotContains(t, clients, "localhost")
|
||||
local.AssertExpectations(t)
|
||||
remote.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_valid_remote_and_local(t *testing.T) {
|
||||
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
||||
client := new(fakeClient)
|
||||
client.On("ListContainers").Return([]docker.Container{}, nil)
|
||||
client.On("Host").Return(&docker.Host{
|
||||
local := new(fakeCLI)
|
||||
local.On("ContainerList").Return([]types.Container{}, nil)
|
||||
fakeLocalClientFactory := func(filter map[string][]string) (*docker.Client, error) {
|
||||
return docker.NewClient(local, filters.NewArgs(), &docker.Host{
|
||||
ID: "localhost",
|
||||
})
|
||||
return client, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
|
||||
client := new(fakeClient)
|
||||
client.On("ListContainers").Return([]docker.Container{}, nil)
|
||||
client.On("Host").Return(&docker.Host{
|
||||
remote := new(fakeCLI)
|
||||
remote.On("ContainerList").Return([]types.Container{}, nil)
|
||||
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (*docker.Client, error) {
|
||||
return docker.NewClient(remote, filters.NewArgs(), &docker.Host{
|
||||
ID: "test",
|
||||
})
|
||||
return client, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
args := args{
|
||||
RemoteHost: []string{"tcp://test:2375"},
|
||||
}
|
||||
@@ -117,24 +111,24 @@ func Test_valid_remote_and_local(t *testing.T) {
|
||||
assert.Equal(t, 2, len(clients))
|
||||
assert.Contains(t, clients, "test")
|
||||
assert.Contains(t, clients, "localhost")
|
||||
local.AssertExpectations(t)
|
||||
remote.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_no_clients(t *testing.T) {
|
||||
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
||||
client := new(fakeClient)
|
||||
client.On("ListContainers").Return([]docker.Container{}, errors.New("error"))
|
||||
client.On("Host").Return(&docker.Host{
|
||||
ID: "localhost",
|
||||
})
|
||||
return client, nil
|
||||
}
|
||||
local := new(fakeCLI)
|
||||
local.On("ContainerList").Return([]types.Container{}, errors.New("error"))
|
||||
fakeLocalClientFactory := func(filter map[string][]string) (*docker.Client, error) {
|
||||
|
||||
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
|
||||
client := new(fakeClient)
|
||||
client.On("Host").Return(&docker.Host{
|
||||
return docker.NewClient(local, filters.NewArgs(), &docker.Host{
|
||||
ID: "localhost",
|
||||
}), nil
|
||||
}
|
||||
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (*docker.Client, error) {
|
||||
client := new(fakeCLI)
|
||||
return docker.NewClient(client, filters.NewArgs(), &docker.Host{
|
||||
ID: "test",
|
||||
})
|
||||
return client, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
args := args{}
|
||||
@@ -142,4 +136,5 @@ func Test_no_clients(t *testing.T) {
|
||||
clients := createClients(args, fakeLocalClientFactory, fakeRemoteClientFactory)
|
||||
|
||||
assert.Equal(t, 0, len(clients))
|
||||
local.AssertExpectations(t)
|
||||
}
|
||||
|
||||
@@ -39,11 +39,11 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
|
||||
for _, client := range h.clients {
|
||||
client.Events(ctx, events)
|
||||
|
||||
go func(client docker.Client) {
|
||||
go func(client DockerClient) {
|
||||
defer wg.Done()
|
||||
if containers, err := client.ListContainers(); err == nil {
|
||||
results <- containers
|
||||
go func(client docker.Client) {
|
||||
go func(client DockerClient) {
|
||||
for _, c := range containers {
|
||||
if c.State == "running" {
|
||||
if err := client.ContainerStats(ctx, c.ID, stats); err != nil && !errors.Is(err, context.Canceled) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -15,6 +17,7 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/analytics"
|
||||
"github.com/amir20/dozzle/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -33,13 +36,24 @@ type Config struct {
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
clients map[string]docker.Client
|
||||
clients map[string]DockerClient
|
||||
content fs.FS
|
||||
config *Config
|
||||
}
|
||||
|
||||
// CreateServer creates a service for http handler
|
||||
func CreateServer(clients map[string]docker.Client, content fs.FS, config Config) *http.Server {
|
||||
// Client is a proxy around the docker client
|
||||
type DockerClient interface {
|
||||
ListContainers() ([]docker.Container, error)
|
||||
FindContainer(string) (docker.Container, error)
|
||||
ContainerLogs(context.Context, string, string, docker.StdType) (io.ReadCloser, error)
|
||||
Events(context.Context, chan<- docker.ContainerEvent) <-chan error
|
||||
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time, docker.StdType) (io.ReadCloser, error)
|
||||
ContainerStats(context.Context, string, chan<- docker.ContainerStat) error
|
||||
Ping(context.Context) (types.Ping, error)
|
||||
Host() *docker.Host
|
||||
}
|
||||
|
||||
func CreateServer(clients map[string]DockerClient, content fs.FS, config Config) *http.Server {
|
||||
handler := &handler{
|
||||
clients: clients,
|
||||
content: content,
|
||||
@@ -98,7 +112,7 @@ func (h *handler) index(w http.ResponseWriter, req *http.Request) {
|
||||
go func() {
|
||||
host, _ := os.Hostname()
|
||||
|
||||
var client docker.Client
|
||||
var client DockerClient
|
||||
for _, v := range h.clients {
|
||||
client = v
|
||||
break
|
||||
@@ -216,7 +230,7 @@ func (h *handler) version(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (h *handler) healthcheck(w http.ResponseWriter, r *http.Request) {
|
||||
log.Trace("Executing healthcheck request")
|
||||
var client docker.Client
|
||||
var client DockerClient
|
||||
for _, v := range h.clients {
|
||||
client = v
|
||||
break
|
||||
@@ -230,7 +244,7 @@ func (h *handler) healthcheck(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) clientFromRequest(r *http.Request) docker.Client {
|
||||
func (h *handler) clientFromRequest(r *http.Request) DockerClient {
|
||||
host := chi.URLParam(r, "host")
|
||||
|
||||
if host == "" {
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
type MockedClient struct {
|
||||
mock.Mock
|
||||
docker.Client
|
||||
DockerClient
|
||||
}
|
||||
|
||||
func (m *MockedClient) FindContainer(id string) (docker.Container, error) {
|
||||
@@ -54,7 +54,7 @@ func (m *MockedClient) Host() *docker.Host {
|
||||
return args.Get(0).(*docker.Host)
|
||||
}
|
||||
|
||||
func createHandler(client docker.Client, content fs.FS, config Config) *chi.Mux {
|
||||
func createHandler(client DockerClient, content fs.FS, config Config) *chi.Mux {
|
||||
if client == nil {
|
||||
client = new(MockedClient)
|
||||
client.(*MockedClient).On("ListContainers").Return([]docker.Container{}, nil)
|
||||
@@ -69,7 +69,7 @@ func createHandler(client docker.Client, content fs.FS, config Config) *chi.Mux
|
||||
content = afero.NewIOFS(fs)
|
||||
}
|
||||
|
||||
clients := map[string]docker.Client{
|
||||
clients := map[string]DockerClient{
|
||||
"localhost": client,
|
||||
}
|
||||
return createRouter(&handler{
|
||||
@@ -79,6 +79,6 @@ func createHandler(client docker.Client, content fs.FS, config Config) *chi.Mux
|
||||
})
|
||||
}
|
||||
|
||||
func createDefaultHandler(client docker.Client) *chi.Mux {
|
||||
func createDefaultHandler(client DockerClient) *chi.Mux {
|
||||
return createHandler(client, nil, Config{Base: "/"})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user