1
0
mirror of https://github.com/amir20/dozzle.git synced 2026-01-04 03:54:58 +01:00

feat!: implements swarm mode with agents (#3058)

This commit is contained in:
Amir Raminfar
2024-07-05 13:38:10 -07:00
committed by GitHub
parent 2e5fb71938
commit 4de9c775ba
70 changed files with 2681 additions and 963 deletions

View File

@@ -63,13 +63,13 @@ type DockerCLI interface {
type Client interface {
ListContainers() ([]Container, error)
FindContainer(string) (Container, error)
ContainerLogs(context.Context, string, *time.Time, StdType) (io.ReadCloser, error)
Events(context.Context, chan<- ContainerEvent) 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(action string, containerID string) error
Host() Host
ContainerActions(action ContainerAction, containerID string) error
IsSwarmMode() bool
SystemInfo() system.Info
}
@@ -77,31 +77,33 @@ type Client interface {
type httpClient struct {
cli DockerCLI
filters filters.Args
host *Host
host Host
info system.Info
}
func NewClient(cli DockerCLI, filters filters.Args, host *Host) Client {
func NewClient(cli DockerCLI, filters filters.Args, host Host) Client {
client := &httpClient{
cli: cli,
filters: filters,
host: host,
}
var err error
client.info, err = cli.Info(context.Background())
if err != nil {
log.Errorf("unable to get docker info: %v", err)
}
if host.MemTotal == 0 || host.NCPU == 0 {
var err error
client.info, err = cli.Info(context.Background())
if err != nil {
log.Errorf("unable to get docker info: %v", err)
}
host.NCPU = client.info.NCPU
host.MemTotal = client.info.MemTotal
host.NCPU = client.info.NCPU
host.MemTotal = client.info.MemTotal
}
return client
}
// NewClientWithFilters creates a new instance of Client with docker filters
func NewClientWithFilters(f map[string][]string) (Client, error) {
func NewLocalClient(f map[string][]string, hostname string) (Client, error) {
filterArgs := filters.NewArgs()
for key, values := range f {
for _, value := range values {
@@ -117,10 +119,27 @@ func NewClientWithFilters(f map[string][]string) (Client, error) {
return nil, err
}
return NewClient(cli, filterArgs, &Host{Name: "localhost", ID: "localhost"}), nil
info, err := cli.Info(context.Background())
if err != nil {
return nil, err
}
host := Host{
ID: info.ID,
Name: info.Name,
MemTotal: info.MemTotal,
NCPU: info.NCPU,
Endpoint: "local",
}
if hostname != "" {
host.Name = hostname
}
return NewClient(cli, filterArgs, host), nil
}
func NewClientWithTlsAndFilter(f map[string][]string, host Host) (Client, error) {
func NewRemoteClient(f map[string][]string, host Host) (Client, error) {
filterArgs := filters.NewArgs()
for key, values := range f {
for _, value := range values {
@@ -153,10 +172,11 @@ func NewClientWithTlsAndFilter(f map[string][]string, host Host) (Client, error)
return nil, err
}
return NewClient(cli, filterArgs, &host), nil
return NewClient(cli, filterArgs, host), nil
}
func (d *httpClient) FindContainer(id string) (Container, error) {
log.Debugf("finding container with id: %s", id)
var container Container
containers, err := d.ListContainers()
if err != nil {
@@ -188,13 +208,13 @@ func (d *httpClient) FindContainer(id string) (Container, error) {
return container, nil
}
func (d *httpClient) ContainerActions(action string, containerID string) error {
func (d *httpClient) ContainerActions(action ContainerAction, containerID string) error {
switch action {
case "start":
case Start:
return d.cli.ContainerStart(context.Background(), containerID, container.StartOptions{})
case "stop":
case Stop:
return d.cli.ContainerStop(context.Background(), containerID, container.StopOptions{})
case "restart":
case Restart:
return d.cli.ContainerRestart(context.Background(), containerID, container.StopOptions{})
default:
return fmt.Errorf("unknown action: %s", action)
@@ -299,14 +319,10 @@ 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 StdType) (io.ReadCloser, error) {
log.WithField("id", id).WithField("since", since).WithField("stdType", stdType).Debug("streaming logs for container")
sinceQuery := ""
if since != nil {
sinceQuery = since.Add(time.Millisecond).Format(time.RFC3339Nano)
}
sinceQuery := since.Add(time.Millisecond).Format(time.RFC3339Nano)
options := container.LogsOptions{
ShowStdout: stdType&STDOUT != 0,
ShowStderr: stdType&STDERR != 0,
@@ -324,7 +340,7 @@ func (d *httpClient) ContainerLogs(ctx context.Context, id string, since *time.T
return reader, nil
}
func (d *httpClient) Events(ctx context.Context, messages chan<- ContainerEvent) error {
func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- ContainerEvent) error {
dockerMessages, err := d.cli.Events(ctx, events.ListOptions{})
for {
@@ -344,7 +360,6 @@ func (d *httpClient) Events(ctx context.Context, messages chan<- ContainerEvent)
}
}
}
}
func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType StdType) (io.ReadCloser, error) {
@@ -370,7 +385,7 @@ func (d *httpClient) Ping(ctx context.Context) (types.Ping, error) {
return d.cli.Ping(ctx)
}
func (d *httpClient) Host() *Host {
func (d *httpClient) Host() Host {
return d.host
}

View File

@@ -65,7 +65,6 @@ func (m *mockedProxy) ContainerStart(ctx context.Context, containerID string, op
}
func (m *mockedProxy) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error {
args := m.Called(ctx, containerID, options)
err := args.Get(0)
@@ -91,7 +90,7 @@ 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, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
client := &httpClient{proxy, filters.NewArgs(), Host{ID: "localhost"}, system.Info{}}
list, err := client.ListContainers()
assert.Empty(t, list, "list should be empty")
@@ -103,7 +102,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 := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
client := &httpClient{proxy, filters.NewArgs(), Host{ID: "localhost"}, system.Info{}}
list, err := client.ListContainers()
assert.Nil(t, list, "list should be nil")
@@ -126,7 +125,7 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
client := &httpClient{proxy, filters.NewArgs(), Host{ID: "localhost"}, system.Info{}}
list, err := client.ListContainers()
require.NoError(t, err, "error should not return an error.")
@@ -160,8 +159,8 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
Since: "2021-01-01T00:00:00.001Z"}
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
logReader, _ := client.ContainerLogs(context.Background(), id, &since, STDALL)
client := &httpClient{proxy, filters.NewArgs(), Host{ID: "localhost"}, system.Info{}}
logReader, _ := client.ContainerLogs(context.Background(), id, since, STDALL)
actual, _ := io.ReadAll(logReader)
assert.Equal(t, string(b), string(actual), "message doesn't match expected")
@@ -174,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, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
client := &httpClient{proxy, filters.NewArgs(), Host{ID: "localhost"}, system.Info{}}
reader, err := client.ContainerLogs(context.Background(), id, nil, STDALL)
reader, err := client.ContainerLogs(context.Background(), id, time.Time{}, STDALL)
assert.Nil(t, reader, "reader should be nil")
assert.Error(t, err, "error should have been returned")
@@ -202,7 +201,7 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{State: state}, Config: &container.Config{Tty: false}}
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
client := &httpClient{proxy, filters.NewArgs(), Host{ID: "localhost"}, system.Info{}}
container, err := client.FindContainer("abcdefghijkl")
require.NoError(t, err, "error should not be thrown")
@@ -225,7 +224,7 @@ func Test_dockerClient_FindContainer_error(t *testing.T) {
proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
client := &httpClient{proxy, filters.NewArgs(), Host{ID: "localhost"}, system.Info{}}
_, err := client.FindContainer("not_valid")
require.Error(t, err, "error should be thrown")
@@ -246,7 +245,7 @@ func Test_dockerClient_ContainerActions_happy(t *testing.T) {
}
proxy := new(mockedProxy)
client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
client := &httpClient{proxy, filters.NewArgs(), Host{ID: "localhost"}, system.Info{}}
state := &types.ContainerState{Status: "running", StartedAt: time.Now().Format(time.RFC3339Nano)}
json := types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{State: state}, Config: &container.Config{Tty: false}}
@@ -264,7 +263,7 @@ func Test_dockerClient_ContainerActions_happy(t *testing.T) {
actions := []string{"start", "stop", "restart"}
for _, action := range actions {
err := client.ContainerActions(action, container.ID)
err := client.ContainerActions(ContainerAction(action), container.ID)
require.NoError(t, err, "error should not be thrown")
assert.Equal(t, err, nil)
}
@@ -285,7 +284,7 @@ func Test_dockerClient_ContainerActions_error(t *testing.T) {
}
proxy := new(mockedProxy)
client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
client := &httpClient{proxy, filters.NewArgs(), Host{ID: "localhost"}, system.Info{}}
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
@@ -297,7 +296,7 @@ func Test_dockerClient_ContainerActions_error(t *testing.T) {
actions := []string{"start", "stop", "restart"}
for _, action := range actions {
err := client.ContainerActions(action, container.ID)
err := client.ContainerActions(ContainerAction(action), container.ID)
require.Error(t, err, "error should be thrown")
assert.Error(t, err, "error should have been returned")
}

View File

@@ -7,13 +7,14 @@ import (
"sync/atomic"
"github.com/puzpuzpuz/xsync/v3"
lop "github.com/samber/lo/parallel"
log "github.com/sirupsen/logrus"
)
type ContainerStore struct {
containers *xsync.MapOf[string, *Container]
subscribers *xsync.MapOf[context.Context, chan ContainerEvent]
newContainerSubscribers *xsync.MapOf[context.Context, chan Container]
subscribers *xsync.MapOf[context.Context, chan<- ContainerEvent]
newContainerSubscribers *xsync.MapOf[context.Context, chan<- Container]
client Client
statsCollector *StatsCollector
wg sync.WaitGroup
@@ -26,8 +27,8 @@ func NewContainerStore(ctx context.Context, client Client) *ContainerStore {
s := &ContainerStore{
containers: xsync.NewMapOf[string, *Container](),
client: client,
subscribers: xsync.NewMapOf[context.Context, chan ContainerEvent](),
newContainerSubscribers: xsync.NewMapOf[context.Context, chan Container](),
subscribers: xsync.NewMapOf[context.Context, chan<- ContainerEvent](),
newContainerSubscribers: xsync.NewMapOf[context.Context, chan<- Container](),
statsCollector: NewStatsCollector(client),
wg: sync.WaitGroup{},
events: make(chan ContainerEvent),
@@ -41,11 +42,13 @@ func NewContainerStore(ctx context.Context, client Client) *ContainerStore {
return s
}
var ErrContainerNotFound = errors.New("container not found")
func (s *ContainerStore) checkConnectivity() error {
if s.connected.CompareAndSwap(false, true) {
go func() {
log.Debugf("subscribing to docker events from container store %s", s.client.Host())
err := s.client.Events(s.ctx, s.events)
err := s.client.ContainerEvents(s.ctx, s.events)
if !errors.Is(err, context.Canceled) {
log.Errorf("docker store unexpectedly disconnected from docker events from %s with %v", s.client.Host(), err)
}
@@ -56,16 +59,17 @@ func (s *ContainerStore) checkConnectivity() error {
return err
} else {
s.containers.Clear()
for _, c := range containers {
s.containers.Store(c.ID, &c)
}
lop.ForEach(containers, func(c Container, _ int) {
container, _ := s.client.FindContainer(c.ID)
s.containers.Store(c.ID, &container)
})
}
}
return nil
}
func (s *ContainerStore) List() ([]Container, error) {
func (s *ContainerStore) ListContainers() ([]Container, error) {
s.wg.Wait()
if err := s.checkConnectivity(); err != nil {
@@ -80,11 +84,27 @@ func (s *ContainerStore) List() ([]Container, error) {
return containers, nil
}
func (s *ContainerStore) FindContainer(id string) (Container, error) {
list, err := s.ListContainers()
if err != nil {
return Container{}, err
}
for _, c := range list {
if c.ID == id {
return c, nil
}
}
log.Warnf("container %s not found in store", id)
return Container{}, ErrContainerNotFound
}
func (s *ContainerStore) Client() Client {
return s.client
}
func (s *ContainerStore) Subscribe(ctx context.Context, events chan ContainerEvent) {
func (s *ContainerStore) SubscribeEvents(ctx context.Context, events chan<- ContainerEvent) {
go func() {
if s.statsCollector.Start(s.ctx) {
log.Debug("clearing container stats as stats collector has been stopped")
@@ -96,19 +116,23 @@ func (s *ContainerStore) Subscribe(ctx context.Context, events chan ContainerEve
}()
s.subscribers.Store(ctx, events)
go func() {
<-ctx.Done()
s.subscribers.Delete(ctx)
s.statsCollector.Stop()
}()
}
func (s *ContainerStore) Unsubscribe(ctx context.Context) {
s.subscribers.Delete(ctx)
s.statsCollector.Stop()
}
func (s *ContainerStore) SubscribeStats(ctx context.Context, stats chan ContainerStat) {
func (s *ContainerStore) SubscribeStats(ctx context.Context, stats chan<- ContainerStat) {
s.statsCollector.Subscribe(ctx, stats)
}
func (s *ContainerStore) SubscribeNewContainers(ctx context.Context, containers chan Container) {
func (s *ContainerStore) SubscribeNewContainers(ctx context.Context, containers chan<- Container) {
s.newContainerSubscribers.Store(ctx, containers)
go func() {
<-ctx.Done()
s.newContainerSubscribers.Delete(ctx)
}()
}
func (s *ContainerStore) init() {
@@ -128,11 +152,10 @@ func (s *ContainerStore) init() {
if container, err := s.client.FindContainer(event.ActorID); err == nil {
log.Debugf("container %s started", container.ID)
s.containers.Store(container.ID, &container)
s.newContainerSubscribers.Range(func(c context.Context, containers chan Container) bool {
s.newContainerSubscribers.Range(func(c context.Context, containers chan<- Container) bool {
select {
case containers <- container:
case <-c.Done():
s.newContainerSubscribers.Delete(c)
}
return true
})
@@ -167,7 +190,7 @@ func (s *ContainerStore) init() {
}
})
}
s.subscribers.Range(func(c context.Context, events chan ContainerEvent) bool {
s.subscribers.Range(func(c context.Context, events chan<- ContainerEvent) bool {
select {
case events <- event:
case <-c.Done():

View File

@@ -24,7 +24,7 @@ func (m *mockedClient) FindContainer(id string) (Container, error) {
return args.Get(0).(Container), args.Error(1)
}
func (m *mockedClient) Events(ctx context.Context, events chan<- ContainerEvent) error {
func (m *mockedClient) ContainerEvents(ctx context.Context, events chan<- ContainerEvent) error {
args := m.Called(ctx, events)
return args.Error(0)
}
@@ -34,9 +34,9 @@ func (m *mockedClient) ContainerStats(ctx context.Context, id string, stats chan
return args.Error(0)
}
func (m *mockedClient) Host() *Host {
func (m *mockedClient) Host() Host {
args := m.Called()
return args.Get(0).(*Host)
return args.Get(0).(Host)
}
func TestContainerStore_List(t *testing.T) {
@@ -48,18 +48,26 @@ func TestContainerStore_List(t *testing.T) {
Name: "test",
},
}, nil)
client.On("Events", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).Run(func(args mock.Arguments) {
ctx := args.Get(0).(context.Context)
<-ctx.Done()
})
client.On("Host").Return(&Host{
client.On("Host").Return(Host{
ID: "localhost",
})
client.On("FindContainer", "1234").Return(Container{
ID: "1234",
Name: "test",
Image: "test",
Stats: utils.NewRingBuffer[ContainerStat](300),
}, nil)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
store := NewContainerStore(ctx, client)
containers, _ := store.List()
containers, _ := store.ListContainers()
assert.Equal(t, containers[0].ID, "1234")
}
@@ -75,7 +83,7 @@ func TestContainerStore_die(t *testing.T) {
},
}, nil)
client.On("Events", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).Return(nil).
Run(func(args mock.Arguments) {
ctx := args.Get(0).(context.Context)
events := args.Get(1).(chan<- ContainerEvent)
@@ -86,21 +94,28 @@ func TestContainerStore_die(t *testing.T) {
}
<-ctx.Done()
})
client.On("Host").Return(&Host{
client.On("Host").Return(Host{
ID: "localhost",
})
client.On("ContainerStats", mock.Anything, "1234", mock.AnythingOfType("chan<- docker.ContainerStat")).Return(nil)
client.On("FindContainer", "1234").Return(Container{
ID: "1234",
Name: "test",
Image: "test",
Stats: utils.NewRingBuffer[ContainerStat](300),
}, nil)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
store := NewContainerStore(ctx, client)
// Wait until we get the event
events := make(chan ContainerEvent)
store.Subscribe(ctx, events)
store.SubscribeEvents(ctx, events)
<-events
containers, _ := store.List()
containers, _ := store.ListContainers()
assert.Equal(t, containers[0].State, "exited")
}

View File

@@ -197,24 +197,24 @@ func checkPosition(currentEvent *LogEvent, nextEvent *LogEvent) {
currentLevel := guessLogLevel(currentEvent)
if nextEvent != nil {
if currentEvent.IsCloseToTime(nextEvent) && currentLevel != "" && !nextEvent.HasLevel() {
currentEvent.Position = START
nextEvent.Position = MIDDLE
currentEvent.Position = Beginning
nextEvent.Position = Middle
}
// If next item is not close to current item or has level, set current item position to end
if currentEvent.Position == MIDDLE && (nextEvent.HasLevel() || !currentEvent.IsCloseToTime(nextEvent)) {
currentEvent.Position = END
if currentEvent.Position == Middle && (nextEvent.HasLevel() || !currentEvent.IsCloseToTime(nextEvent)) {
currentEvent.Position = End
}
// If next item is close to current item and has no level, set next item position to middle
if currentEvent.Position == MIDDLE && !nextEvent.HasLevel() && currentEvent.IsCloseToTime(nextEvent) {
nextEvent.Position = MIDDLE
if currentEvent.Position == Middle && !nextEvent.HasLevel() && currentEvent.IsCloseToTime(nextEvent) {
nextEvent.Position = Middle
}
// Set next item level to current item level
if currentEvent.Position == START || currentEvent.Position == MIDDLE {
if currentEvent.Position == Beginning || currentEvent.Position == Middle {
nextEvent.Level = currentEvent.Level
}
} else if currentEvent.Position == MIDDLE {
currentEvent.Position = END
} else if currentEvent.Position == Middle {
currentEvent.Position = End
}
}

View File

@@ -6,7 +6,7 @@ import (
"os"
"path/filepath"
"strings"
log "github.com/sirupsen/logrus"
)
@@ -20,10 +20,11 @@ type Host struct {
ValidCerts bool `json:"-"`
NCPU int `json:"nCPU"`
MemTotal int64 `json:"memTotal"`
Endpoint string `json:"endpoint"`
}
func (h *Host) String() string {
return h.ID
func (h Host) String() string {
return fmt.Sprintf("ID: %s, Endpoint: %s", h.ID, h.Endpoint)
}
func ParseConnection(connection string) (Host, error) {
@@ -72,6 +73,7 @@ func ParseConnection(connection string) (Host, error) {
CACertPath: cacertPath,
KeyPath: keyPath,
ValidCerts: hasCerts,
Endpoint: remoteUrl.String(),
}, nil
}

View File

@@ -14,7 +14,7 @@ import (
type StatsCollector struct {
stream chan ContainerStat
subscribers *xsync.MapOf[context.Context, chan ContainerStat]
subscribers *xsync.MapOf[context.Context, chan<- ContainerStat]
client Client
cancelers *xsync.MapOf[string, context.CancelFunc]
stopper context.CancelFunc
@@ -28,14 +28,18 @@ var timeToStop = 6 * time.Hour
func NewStatsCollector(client Client) *StatsCollector {
return &StatsCollector{
stream: make(chan ContainerStat),
subscribers: xsync.NewMapOf[context.Context, chan ContainerStat](),
subscribers: xsync.NewMapOf[context.Context, chan<- ContainerStat](),
client: client,
cancelers: xsync.NewMapOf[string, context.CancelFunc](),
}
}
func (c *StatsCollector) Subscribe(ctx context.Context, stats chan ContainerStat) {
func (c *StatsCollector) Subscribe(ctx context.Context, stats chan<- ContainerStat) {
c.subscribers.Store(ctx, stats)
go func() {
<-ctx.Done()
c.subscribers.Delete(ctx)
}()
}
func (c *StatsCollector) forceStop() {
@@ -109,7 +113,7 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
go func() {
log.Debugf("subscribing to docker events from stats collector %s", sc.client.Host())
err := sc.client.Events(context.Background(), events)
err := sc.client.ContainerEvents(context.Background(), events)
if !errors.Is(err, context.Canceled) {
log.Errorf("stats collector unexpectedly disconnected from docker events from %s with %v", sc.client.Host(), err)
}
@@ -136,7 +140,7 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool {
log.Info("stopped collecting container stats")
return true
case stat := <-sc.stream:
sc.subscribers.Range(func(c context.Context, stats chan ContainerStat) bool {
sc.subscribers.Range(func(c context.Context, stats chan<- ContainerStat) bool {
select {
case stats <- stat:
case <-c.Done():

View File

@@ -17,7 +17,7 @@ func startedCollector(ctx context.Context) *StatsCollector {
State: "running",
},
}, nil)
client.On("Events", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).
client.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- docker.ContainerEvent")).
Return(nil).
Run(func(args mock.Arguments) {
ctx := args.Get(0).(context.Context)
@@ -31,7 +31,7 @@ func startedCollector(ctx context.Context) *StatsCollector {
ID: "1234",
}
})
client.On("Host").Return(&Host{
client.On("Host").Return(Host{
ID: "localhost",
})

View File

@@ -1,6 +1,7 @@
package docker
import (
"fmt"
"math"
"time"
@@ -45,11 +46,29 @@ type ContainerEvent struct {
type LogPosition string
const (
START LogPosition = "start"
MIDDLE LogPosition = "middle"
END LogPosition = "end"
Beginning LogPosition = "start"
Middle LogPosition = "middle"
End LogPosition = "end"
)
type ContainerAction string
const (
Start ContainerAction = "start"
Stop ContainerAction = "stop"
Restart ContainerAction = "restart"
)
func ParseContainerAction(input string) (ContainerAction, error) {
action := ContainerAction(input)
switch action {
case Start, Stop, Restart:
return action, nil
default:
return "", fmt.Errorf("unknown action: %s", input)
}
}
type LogEvent struct {
Message any `json:"m,omitempty"`
Timestamp int64 `json:"ts"`