mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-21 21:33:18 +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"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dockerClient struct {
|
|
||||||
cli dockerProxy
|
|
||||||
filters filters.Args
|
|
||||||
host *Host
|
|
||||||
}
|
|
||||||
|
|
||||||
type StdType int
|
type StdType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -48,7 +42,7 @@ func (s StdType) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type dockerProxy interface {
|
type DockerCLI interface {
|
||||||
ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error)
|
ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error)
|
||||||
ContainerLogs(context.Context, string, types.ContainerLogsOptions) (io.ReadCloser, error)
|
ContainerLogs(context.Context, string, types.ContainerLogsOptions) (io.ReadCloser, error)
|
||||||
Events(context.Context, types.EventsOptions) (<-chan events.Message, <-chan 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)
|
Ping(ctx context.Context) (types.Ping, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is a proxy around the docker client
|
type Client struct {
|
||||||
type Client interface {
|
cli DockerCLI
|
||||||
ListContainers() ([]Container, error)
|
filters filters.Args
|
||||||
FindContainer(string) (Container, error)
|
host *Host
|
||||||
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)
|
func NewClient(cli DockerCLI, filters filters.Args, host *Host) *Client {
|
||||||
ContainerStats(context.Context, string, chan<- ContainerStat) error
|
return &Client{cli, filters, host}
|
||||||
Ping(context.Context) (types.Ping, error)
|
|
||||||
Host() *Host
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientWithFilters creates a new instance of Client with docker filters
|
// 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()
|
filterArgs := filters.NewArgs()
|
||||||
for key, values := range f {
|
for key, values := range f {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
@@ -86,10 +78,10 @@ func NewClientWithFilters(f map[string][]string) (Client, error) {
|
|||||||
return nil, err
|
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()
|
filterArgs := filters.NewArgs()
|
||||||
for key, values := range f {
|
for key, values := range f {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
@@ -122,10 +114,10 @@ func NewClientWithTlsAndFilter(f map[string][]string, host Host) (Client, error)
|
|||||||
return nil, err
|
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
|
var container Container
|
||||||
containers, err := d.ListContainers()
|
containers, err := d.ListContainers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -153,7 +145,7 @@ func (d *dockerClient) FindContainer(id string) (Container, error) {
|
|||||||
return container, nil
|
return container, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dockerClient) ListContainers() ([]Container, error) {
|
func (d *Client) ListContainers() ([]Container, error) {
|
||||||
containerListOptions := types.ContainerListOptions{
|
containerListOptions := types.ContainerListOptions{
|
||||||
Filters: d.filters,
|
Filters: d.filters,
|
||||||
All: true,
|
All: true,
|
||||||
@@ -188,7 +180,7 @@ func (d *dockerClient) ListContainers() ([]Container, error) {
|
|||||||
return containers, nil
|
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)
|
response, err := d.cli.ContainerStats(ctx, id, true)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -240,7 +232,7 @@ func (d *dockerClient) ContainerStats(ctx context.Context, id string, stats chan
|
|||||||
return nil
|
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")
|
log.WithField("id", id).WithField("since", since).WithField("stdType", stdType).Debug("streaming logs for container")
|
||||||
|
|
||||||
if since != "" {
|
if since != "" {
|
||||||
@@ -268,7 +260,7 @@ func (d *dockerClient) ContainerLogs(ctx context.Context, id string, since strin
|
|||||||
return reader, nil
|
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{})
|
dockerMessages, errors := d.cli.Events(ctx, types.EventsOptions{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -296,7 +288,7 @@ func (d *dockerClient) Events(ctx context.Context, messages chan<- ContainerEven
|
|||||||
return errors
|
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{
|
options := types.ContainerLogsOptions{
|
||||||
ShowStdout: stdType&STDOUT != 0,
|
ShowStdout: stdType&STDOUT != 0,
|
||||||
ShowStderr: stdType&STDERR != 0,
|
ShowStderr: stdType&STDERR != 0,
|
||||||
@@ -315,11 +307,11 @@ func (d *dockerClient) ContainerLogsBetweenDates(ctx context.Context, id string,
|
|||||||
return reader, nil
|
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)
|
return d.cli.Ping(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dockerClient) Host() *Host {
|
func (d *Client) Host() *Host {
|
||||||
return d.host
|
return d.host
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
|
|
||||||
type mockedProxy struct {
|
type mockedProxy struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
dockerProxy
|
DockerCLI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockedProxy) ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
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) {
|
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
||||||
client := &dockerClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||||
|
|
||||||
list, err := client.ListContainers()
|
list, err := client.ListContainers()
|
||||||
assert.Empty(t, list, "list should be empty")
|
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) {
|
func Test_dockerClient_ListContainers_error(t *testing.T) {
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
|
||||||
client := &dockerClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||||
|
|
||||||
list, err := client.ListContainers()
|
list, err := client.ListContainers()
|
||||||
assert.Nil(t, list, "list should be nil")
|
assert.Nil(t, list, "list should be nil")
|
||||||
@@ -88,7 +88,7 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
|
|||||||
|
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
||||||
client := &dockerClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||||
|
|
||||||
list, err := client.ListContainers()
|
list, err := client.ListContainers()
|
||||||
require.NoError(t, err, "error should not return an error.")
|
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"}
|
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)
|
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)
|
logReader, _ := client.ContainerLogs(context.Background(), id, "since", STDALL)
|
||||||
|
|
||||||
actual, _ := io.ReadAll(logReader)
|
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"))
|
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)
|
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}}
|
json := types.ContainerJSON{Config: &container.Config{Tty: false}}
|
||||||
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
|
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")
|
container, err := client.FindContainer("abcdefghijkl")
|
||||||
require.NoError(t, err, "error should not be thrown")
|
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 := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
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")
|
_, err := client.FindContainer("not_valid")
|
||||||
require.Error(t, err, "error should be thrown")
|
require.Error(t, err, "error should be thrown")
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -197,38 +196,3 @@ func checkPosition(currentEvent *LogEvent, nextEvent *LogEvent) {
|
|||||||
currentEvent.Position = END
|
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 {
|
func createClients(args args,
|
||||||
clients := make(map[string]docker.Client)
|
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 {
|
if localClient := createLocalClient(args, localClientFactory); localClient != nil {
|
||||||
clients[localClient.Host().ID] = localClient
|
clients[localClient.Host().ID] = localClient
|
||||||
@@ -166,7 +168,7 @@ func createClients(args args, localClientFactory func(map[string][]string) (dock
|
|||||||
return clients
|
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")
|
_, dev := os.LookupEnv("DEV")
|
||||||
config := web.Config{
|
config := web.Config{
|
||||||
Addr: args.Addr,
|
Addr: args.Addr,
|
||||||
@@ -206,7 +208,7 @@ func createServer(args args, clients map[string]docker.Client) *http.Server {
|
|||||||
return web.CreateServer(clients, assets, config)
|
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++ {
|
for i := 1; ; i++ {
|
||||||
dockerClient, err := localClientFactory(args.Filter)
|
dockerClient, err := localClientFactory(args.Filter)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
115
main_test.go
115
main_test.go
@@ -1,37 +1,34 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/amir20/dozzle/docker"
|
"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/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeClient struct {
|
type fakeCLI struct {
|
||||||
docker.Client
|
docker.DockerCLI
|
||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeClient) ListContainers() ([]docker.Container, error) {
|
func (f *fakeCLI) ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
||||||
args := f.Called()
|
args := f.Called()
|
||||||
return args.Get(0).([]docker.Container), args.Error(1)
|
return args.Get(0).([]types.Container), args.Error(1)
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeClient) Host() *docker.Host {
|
|
||||||
args := f.Called()
|
|
||||||
return args.Get(0).(*docker.Host)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_valid_localhost(t *testing.T) {
|
func Test_valid_localhost(t *testing.T) {
|
||||||
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
client := new(fakeCLI)
|
||||||
client := new(fakeClient)
|
client.On("ContainerList").Return([]types.Container{}, nil)
|
||||||
client.On("ListContainers").Return([]docker.Container{}, nil)
|
fakeClientFactory := func(filter map[string][]string) (*docker.Client, error) {
|
||||||
client.On("Host").Return(&docker.Host{
|
return docker.NewClient(client, filters.NewArgs(), &docker.Host{
|
||||||
ID: "localhost",
|
ID: "localhost",
|
||||||
})
|
}), nil
|
||||||
return client, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args := args{}
|
args := args{}
|
||||||
@@ -39,16 +36,16 @@ func Test_valid_localhost(t *testing.T) {
|
|||||||
actualClient := createLocalClient(args, fakeClientFactory)
|
actualClient := createLocalClient(args, fakeClientFactory)
|
||||||
|
|
||||||
assert.NotNil(t, actualClient)
|
assert.NotNil(t, actualClient)
|
||||||
|
client.AssertExpectations(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_invalid_localhost(t *testing.T) {
|
func Test_invalid_localhost(t *testing.T) {
|
||||||
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
client := new(fakeCLI)
|
||||||
client := new(fakeClient)
|
client.On("ContainerList").Return([]types.Container{}, errors.New("error"))
|
||||||
client.On("ListContainers").Return([]docker.Container{}, errors.New("error"))
|
fakeClientFactory := func(filter map[string][]string) (*docker.Client, error) {
|
||||||
client.On("Host").Return(&docker.Host{
|
return docker.NewClient(client, filters.NewArgs(), &docker.Host{
|
||||||
ID: "localhost",
|
ID: "localhost",
|
||||||
})
|
}), nil
|
||||||
return client, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args := args{}
|
args := args{}
|
||||||
@@ -56,26 +53,24 @@ func Test_invalid_localhost(t *testing.T) {
|
|||||||
actualClient := createLocalClient(args, fakeClientFactory)
|
actualClient := createLocalClient(args, fakeClientFactory)
|
||||||
|
|
||||||
assert.Nil(t, actualClient)
|
assert.Nil(t, actualClient)
|
||||||
|
client.AssertExpectations(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_valid_remote(t *testing.T) {
|
func Test_valid_remote(t *testing.T) {
|
||||||
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
local := new(fakeCLI)
|
||||||
client := new(fakeClient)
|
local.On("ContainerList").Return([]types.Container{}, errors.New("error"))
|
||||||
client.On("ListContainers").Return([]docker.Container{}, errors.New("error"))
|
fakeLocalClientFactory := func(filter map[string][]string) (*docker.Client, error) {
|
||||||
client.On("Host").Return(&docker.Host{
|
return docker.NewClient(local, filters.NewArgs(), &docker.Host{
|
||||||
ID: "localhost",
|
ID: "localhost",
|
||||||
})
|
}), nil
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
|
remote := new(fakeCLI)
|
||||||
client := new(fakeClient)
|
remote.On("ContainerList").Return([]types.Container{}, nil)
|
||||||
client.On("ListContainers").Return([]docker.Container{}, nil)
|
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (*docker.Client, error) {
|
||||||
client.On("Host").Return(&docker.Host{
|
return docker.NewClient(remote, filters.NewArgs(), &docker.Host{
|
||||||
ID: "test",
|
ID: "test",
|
||||||
})
|
}), nil
|
||||||
return client, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args := args{
|
args := args{
|
||||||
@@ -87,27 +82,26 @@ func Test_valid_remote(t *testing.T) {
|
|||||||
assert.Equal(t, 1, len(clients))
|
assert.Equal(t, 1, len(clients))
|
||||||
assert.Contains(t, clients, "test")
|
assert.Contains(t, clients, "test")
|
||||||
assert.NotContains(t, clients, "localhost")
|
assert.NotContains(t, clients, "localhost")
|
||||||
|
local.AssertExpectations(t)
|
||||||
|
remote.AssertExpectations(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_valid_remote_and_local(t *testing.T) {
|
func Test_valid_remote_and_local(t *testing.T) {
|
||||||
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
local := new(fakeCLI)
|
||||||
client := new(fakeClient)
|
local.On("ContainerList").Return([]types.Container{}, nil)
|
||||||
client.On("ListContainers").Return([]docker.Container{}, nil)
|
fakeLocalClientFactory := func(filter map[string][]string) (*docker.Client, error) {
|
||||||
client.On("Host").Return(&docker.Host{
|
return docker.NewClient(local, filters.NewArgs(), &docker.Host{
|
||||||
ID: "localhost",
|
ID: "localhost",
|
||||||
})
|
}), nil
|
||||||
return client, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
|
remote := new(fakeCLI)
|
||||||
client := new(fakeClient)
|
remote.On("ContainerList").Return([]types.Container{}, nil)
|
||||||
client.On("ListContainers").Return([]docker.Container{}, nil)
|
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (*docker.Client, error) {
|
||||||
client.On("Host").Return(&docker.Host{
|
return docker.NewClient(remote, filters.NewArgs(), &docker.Host{
|
||||||
ID: "test",
|
ID: "test",
|
||||||
})
|
}), nil
|
||||||
return client, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args := args{
|
args := args{
|
||||||
RemoteHost: []string{"tcp://test:2375"},
|
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.Equal(t, 2, len(clients))
|
||||||
assert.Contains(t, clients, "test")
|
assert.Contains(t, clients, "test")
|
||||||
assert.Contains(t, clients, "localhost")
|
assert.Contains(t, clients, "localhost")
|
||||||
|
local.AssertExpectations(t)
|
||||||
|
remote.AssertExpectations(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_no_clients(t *testing.T) {
|
func Test_no_clients(t *testing.T) {
|
||||||
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
local := new(fakeCLI)
|
||||||
client := new(fakeClient)
|
local.On("ContainerList").Return([]types.Container{}, errors.New("error"))
|
||||||
client.On("ListContainers").Return([]docker.Container{}, errors.New("error"))
|
fakeLocalClientFactory := func(filter map[string][]string) (*docker.Client, error) {
|
||||||
client.On("Host").Return(&docker.Host{
|
|
||||||
ID: "localhost",
|
|
||||||
})
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
|
return docker.NewClient(local, filters.NewArgs(), &docker.Host{
|
||||||
client := new(fakeClient)
|
ID: "localhost",
|
||||||
client.On("Host").Return(&docker.Host{
|
}), 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",
|
ID: "test",
|
||||||
})
|
}), nil
|
||||||
return client, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args := args{}
|
args := args{}
|
||||||
@@ -142,4 +136,5 @@ func Test_no_clients(t *testing.T) {
|
|||||||
clients := createClients(args, fakeLocalClientFactory, fakeRemoteClientFactory)
|
clients := createClients(args, fakeLocalClientFactory, fakeRemoteClientFactory)
|
||||||
|
|
||||||
assert.Equal(t, 0, len(clients))
|
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 {
|
for _, client := range h.clients {
|
||||||
client.Events(ctx, events)
|
client.Events(ctx, events)
|
||||||
|
|
||||||
go func(client docker.Client) {
|
go func(client DockerClient) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if containers, err := client.ListContainers(); err == nil {
|
if containers, err := client.ListContainers(); err == nil {
|
||||||
results <- containers
|
results <- containers
|
||||||
go func(client docker.Client) {
|
go func(client DockerClient) {
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
if c.State == "running" {
|
if c.State == "running" {
|
||||||
if err := client.ContainerStats(ctx, c.ID, stats); err != nil && !errors.Is(err, context.Canceled) {
|
if err := client.ContainerStats(ctx, c.ID, stats); err != nil && !errors.Is(err, context.Canceled) {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"sort"
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -15,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"github.com/amir20/dozzle/analytics"
|
"github.com/amir20/dozzle/analytics"
|
||||||
"github.com/amir20/dozzle/docker"
|
"github.com/amir20/dozzle/docker"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -33,13 +36,24 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
clients map[string]docker.Client
|
clients map[string]DockerClient
|
||||||
content fs.FS
|
content fs.FS
|
||||||
config *Config
|
config *Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateServer creates a service for http handler
|
// Client is a proxy around the docker client
|
||||||
func CreateServer(clients map[string]docker.Client, content fs.FS, config Config) *http.Server {
|
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{
|
handler := &handler{
|
||||||
clients: clients,
|
clients: clients,
|
||||||
content: content,
|
content: content,
|
||||||
@@ -98,7 +112,7 @@ func (h *handler) index(w http.ResponseWriter, req *http.Request) {
|
|||||||
go func() {
|
go func() {
|
||||||
host, _ := os.Hostname()
|
host, _ := os.Hostname()
|
||||||
|
|
||||||
var client docker.Client
|
var client DockerClient
|
||||||
for _, v := range h.clients {
|
for _, v := range h.clients {
|
||||||
client = v
|
client = v
|
||||||
break
|
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) {
|
func (h *handler) healthcheck(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Trace("Executing healthcheck request")
|
log.Trace("Executing healthcheck request")
|
||||||
var client docker.Client
|
var client DockerClient
|
||||||
for _, v := range h.clients {
|
for _, v := range h.clients {
|
||||||
client = v
|
client = v
|
||||||
break
|
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")
|
host := chi.URLParam(r, "host")
|
||||||
|
|
||||||
if host == "" {
|
if host == "" {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
|
|
||||||
type MockedClient struct {
|
type MockedClient struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
docker.Client
|
DockerClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockedClient) FindContainer(id string) (docker.Container, error) {
|
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)
|
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 {
|
if client == nil {
|
||||||
client = new(MockedClient)
|
client = new(MockedClient)
|
||||||
client.(*MockedClient).On("ListContainers").Return([]docker.Container{}, nil)
|
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)
|
content = afero.NewIOFS(fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
clients := map[string]docker.Client{
|
clients := map[string]DockerClient{
|
||||||
"localhost": client,
|
"localhost": client,
|
||||||
}
|
}
|
||||||
return createRouter(&handler{
|
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: "/"})
|
return createHandler(client, nil, Config{Base: "/"})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user