1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-24 06:28:42 +01:00

chore: send server id by loading system info (#2936)

This commit is contained in:
Amir Raminfar
2024-05-07 18:18:51 -07:00
committed by GitHub
parent d37c1395cb
commit 8a2640cd6b
9 changed files with 116 additions and 60 deletions

View File

@@ -1,10 +1,10 @@
/* eslint-disable */ /* eslint-disable */
/* prettier-ignore */
// @ts-nocheck // @ts-nocheck
// Generated by unplugin-vue-components // Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399 // Read more: https://github.com/vuejs/core/pull/3399
export {} export {}
/* prettier-ignore */
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
BarChart: typeof import('./components/BarChart.vue')['default'] BarChart: typeof import('./components/BarChart.vue')['default']

View File

@@ -13,4 +13,6 @@ type BeaconEvent struct {
RunningContainers int `json:"runningContainers"` RunningContainers int `json:"runningContainers"`
HasActions bool `json:"hasActions"` HasActions bool `json:"hasActions"`
IsSwarmMode bool `json:"isSwarmMode"` IsSwarmMode bool `json:"isSwarmMode"`
ServerVersion string `json:"serverVersion"`
ServerID string `json:"serverID"`
} }

View File

@@ -19,6 +19,7 @@ import (
"github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/client" "github.com/docker/docker/client"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -56,6 +57,7 @@ type DockerCLI interface {
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error
ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error
ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error
Info(ctx context.Context) (system.Info, error)
} }
type Client interface { type Client interface {
@@ -69,17 +71,33 @@ type Client interface {
Host() *Host Host() *Host
ContainerActions(action string, containerID string) error ContainerActions(action string, containerID string) error
IsSwarmMode() bool IsSwarmMode() bool
SystemInfo() system.Info
} }
type httpClient struct { type httpClient struct {
cli DockerCLI cli DockerCLI
filters filters.Args filters filters.Args
host *Host host *Host
SwarmMode bool info system.Info
} }
func NewClient(cli DockerCLI, filters filters.Args, host *Host, swarm bool) Client { func NewClient(cli DockerCLI, filters filters.Args, host *Host) Client {
return &httpClient{cli, filters, host, swarm} 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)
}
host.NCPU = client.info.NCPU
host.MemTotal = client.info.MemTotal
return client
} }
// NewClientWithFilters creates a new instance of Client with docker filters // NewClientWithFilters creates a new instance of Client with docker filters
@@ -99,10 +117,7 @@ func NewClientWithFilters(f map[string][]string) (Client, error) {
return nil, err return nil, err
} }
info, _ := cli.Info(context.Background()) return NewClient(cli, filterArgs, &Host{Name: "localhost", ID: "localhost"}), nil
swarm := info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive
return NewClient(cli, filterArgs, &Host{Name: "localhost", ID: "localhost"}, swarm), nil
} }
func NewClientWithTlsAndFilter(f map[string][]string, host Host) (Client, error) { func NewClientWithTlsAndFilter(f map[string][]string, host Host) (Client, error) {
@@ -138,10 +153,7 @@ func NewClientWithTlsAndFilter(f map[string][]string, host Host) (Client, error)
return nil, err return nil, err
} }
info, _ := cli.Info(context.Background()) return NewClient(cli, filterArgs, &host), nil
swarm := info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive
return NewClient(cli, filterArgs, &host, swarm), nil
} }
func (d *httpClient) FindContainer(id string) (Container, error) { func (d *httpClient) FindContainer(id string) (Container, error) {
@@ -355,7 +367,11 @@ func (d *httpClient) Host() *Host {
} }
func (d *httpClient) IsSwarmMode() bool { func (d *httpClient) IsSwarmMode() bool {
return d.SwarmMode return d.info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive
}
func (d *httpClient) SystemInfo() system.Info {
return d.info
} }
var PARENTHESIS_RE = regexp.MustCompile(`\(([a-zA-Z]+)\)`) var PARENTHESIS_RE = regexp.MustCompile(`\(([a-zA-Z]+)\)`)

View File

@@ -12,6 +12,7 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/system"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -89,7 +90,7 @@ func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string,
func Test_dockerClient_ListContainers_null(t *testing.T) { func Test_dockerClient_ListContainers_null(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
list, err := client.ListContainers() list, err := client.ListContainers()
assert.Empty(t, list, "list should be empty") assert.Empty(t, list, "list should be empty")
@@ -101,7 +102,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 := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
list, err := client.ListContainers() list, err := client.ListContainers()
assert.Nil(t, list, "list should be nil") assert.Nil(t, list, "list should be nil")
@@ -124,7 +125,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 := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
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.")
@@ -151,7 +152,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
options := container.LogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "300", Timestamps: true, Since: "since"} options := container.LogsOptions{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 := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
logReader, _ := client.ContainerLogs(context.Background(), id, "since", STDALL) logReader, _ := client.ContainerLogs(context.Background(), id, "since", STDALL)
actual, _ := io.ReadAll(logReader) actual, _ := io.ReadAll(logReader)
@@ -165,7 +166,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 := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
reader, err := client.ContainerLogs(context.Background(), id, "", STDALL) reader, err := client.ContainerLogs(context.Background(), id, "", STDALL)
@@ -192,7 +193,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 := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
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")
@@ -215,7 +216,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 := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
_, 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")
@@ -236,7 +237,7 @@ func Test_dockerClient_ContainerActions_happy(t *testing.T) {
} }
proxy := new(mockedProxy) proxy := new(mockedProxy)
client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
json := types.ContainerJSON{Config: &container.Config{Tty: false}} json := types.ContainerJSON{Config: &container.Config{Tty: false}}
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil) proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
@@ -272,7 +273,7 @@ func Test_dockerClient_ContainerActions_error(t *testing.T) {
} }
proxy := new(mockedProxy) proxy := new(mockedProxy)
client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test")) proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))

View File

@@ -17,6 +17,8 @@ type Host struct {
CACertPath string `json:"-"` CACertPath string `json:"-"`
KeyPath string `json:"-"` KeyPath string `json:"-"`
ValidCerts bool `json:"-"` ValidCerts bool `json:"-"`
NCPU int `json:"nCPU"`
MemTotal int64 `json:"memTotal"`
} }
func (h *Host) String() string { func (h *Host) String() string {

View File

@@ -27,22 +27,9 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
b := analytics.BeaconEvent{
Name: "events",
Version: h.config.Version,
Browser: r.Header.Get("User-Agent"),
AuthProvider: string(h.config.Authorization.Provider),
HasHostname: h.config.Hostname != "",
HasCustomBase: h.config.Base != "/",
HasCustomAddress: h.config.Addr != ":8080",
Clients: len(h.clients),
HasActions: h.config.EnableActions,
}
allContainers := make([]docker.Container, 0) allContainers := make([]docker.Container, 0)
events := make(chan docker.ContainerEvent) events := make(chan docker.ContainerEvent)
stats := make(chan docker.ContainerStat) stats := make(chan docker.ContainerStat)
hasSwarm := false
for _, store := range h.stores { for _, store := range h.stores {
if containers, err := store.List(); err == nil { if containers, err := store.List(); err == nil {
@@ -57,9 +44,6 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
store.SubscribeStats(ctx, stats) store.SubscribeStats(ctx, stats)
store.Subscribe(ctx, events) store.Subscribe(ctx, events)
if store.Client().IsSwarmMode() {
hasSwarm = true
}
} }
defer func() { defer func() {
@@ -71,18 +55,10 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
if err := sendContainersJSON(allContainers, w); err != nil { if err := sendContainersJSON(allContainers, w); err != nil {
log.Errorf("error writing containers to event stream: %v", err) log.Errorf("error writing containers to event stream: %v", err)
} }
b.RunningContainers = len(allContainers)
b.IsSwarmMode = hasSwarm
f.Flush() f.Flush()
if !h.config.NoAnalytics { go sendBeaconEvent(h, r, len(allContainers))
go func() {
if err := analytics.SendBeacon(b); err != nil {
log.Debugf("error sending beacon: %v", err)
}
}()
}
for { for {
select { select {
@@ -141,6 +117,44 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
} }
} }
func sendBeaconEvent(h *handler, r *http.Request, runningContainers int) {
b := analytics.BeaconEvent{
Name: "events",
Version: h.config.Version,
Browser: r.Header.Get("User-Agent"),
AuthProvider: string(h.config.Authorization.Provider),
HasHostname: h.config.Hostname != "",
HasCustomBase: h.config.Base != "/",
HasCustomAddress: h.config.Addr != ":8080",
Clients: len(h.clients),
HasActions: h.config.EnableActions,
RunningContainers: runningContainers,
}
for _, store := range h.stores {
if store.Client().IsSwarmMode() {
b.IsSwarmMode = true
break
}
}
if client, ok := h.clients["localhost"]; ok {
b.ServerID = client.SystemInfo().ID
} else {
for _, client := range h.clients {
b.ServerID = client.SystemInfo().ID
break
}
}
if !h.config.NoAnalytics {
if err := analytics.SendBeacon(b); err != nil {
log.Debugf("error sending beacon: %v", err)
}
}
}
func sendContainersJSON(containers []docker.Container, w http.ResponseWriter) error { func sendContainersJSON(containers []docker.Container, w http.ResponseWriter) error {
if _, err := fmt.Fprint(w, "event: containers-changed\ndata: "); err != nil { if _, err := fmt.Fprint(w, "event: containers-changed\ndata: "); err != nil {
return err return err

View File

@@ -8,6 +8,7 @@ import (
"io/fs" "io/fs"
"github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/docker"
"github.com/docker/docker/api/types/system"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
@@ -63,6 +64,10 @@ func (m *MockedClient) IsSwarmMode() bool {
return false return false
} }
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 docker.Client, content fs.FS, config Config) *chi.Mux {
if client == nil { if client == nil {
client = new(MockedClient) client = new(MockedClient)

15
main.go
View File

@@ -83,7 +83,7 @@ func main() {
} }
srv := createServer(args, clients) srv := createServer(args, clients)
go doStartEvent(args) go doStartEvent(args, clients)
go func() { go func() {
log.Infof("Accepting connections on %s", srv.Addr) log.Infof("Accepting connections on %s", srv.Addr)
if err := srv.ListenAndServe(); err != http.ErrServerClosed { if err := srv.ListenAndServe(); err != http.ErrServerClosed {
@@ -104,7 +104,7 @@ func main() {
log.Debug("shutdown complete") log.Debug("shutdown complete")
} }
func doStartEvent(arg args) { func doStartEvent(arg args, clients map[string]docker.Client) {
if arg.NoAnalytics { if arg.NoAnalytics {
log.Debug("Analytics disabled.") log.Debug("Analytics disabled.")
return return
@@ -115,6 +115,17 @@ func doStartEvent(arg args) {
Version: version, Version: version,
} }
if client, ok := clients["localhost"]; ok {
event.ServerID = client.SystemInfo().ID
event.ServerVersion = client.SystemInfo().ServerVersion
} else {
for _, client := range clients {
event.ServerID = client.SystemInfo().ID
event.ServerVersion = client.SystemInfo().ServerVersion
break
}
}
if err := analytics.SendBeacon(event); err != nil { if err := analytics.SendBeacon(event); err != nil {
log.Debug(err) log.Debug(err)
} }

View File

@@ -9,6 +9,7 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/system"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
@@ -23,13 +24,17 @@ func (f *fakeCLI) ContainerList(context.Context, container.ListOptions) ([]types
return args.Get(0).([]types.Container), args.Error(1) return args.Get(0).([]types.Container), args.Error(1)
} }
func (f *fakeCLI) Info(context.Context) (system.Info, error) {
return system.Info{}, nil
}
func Test_valid_localhost(t *testing.T) { func Test_valid_localhost(t *testing.T) {
client := new(fakeCLI) client := new(fakeCLI)
client.On("ContainerList").Return([]types.Container{}, nil) client.On("ContainerList").Return([]types.Container{}, nil)
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) { fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
return docker.NewClient(client, filters.NewArgs(), &docker.Host{ return docker.NewClient(client, filters.NewArgs(), &docker.Host{
ID: "localhost", ID: "localhost",
}, false), nil }), nil
} }
args := args{} args := args{}
@@ -46,7 +51,7 @@ func Test_invalid_localhost(t *testing.T) {
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) { fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
return docker.NewClient(client, filters.NewArgs(), &docker.Host{ return docker.NewClient(client, filters.NewArgs(), &docker.Host{
ID: "localhost", ID: "localhost",
}, false), nil }), nil
} }
args := args{} args := args{}
@@ -63,7 +68,7 @@ func Test_valid_remote(t *testing.T) {
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) { fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
return docker.NewClient(local, filters.NewArgs(), &docker.Host{ return docker.NewClient(local, filters.NewArgs(), &docker.Host{
ID: "localhost", ID: "localhost",
}, false), nil }), nil
} }
remote := new(fakeCLI) remote := new(fakeCLI)
@@ -71,7 +76,7 @@ func Test_valid_remote(t *testing.T) {
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) { fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
return docker.NewClient(remote, filters.NewArgs(), &docker.Host{ return docker.NewClient(remote, filters.NewArgs(), &docker.Host{
ID: "test", ID: "test",
}, false), nil }), nil
} }
args := args{ args := args{
@@ -93,7 +98,7 @@ func Test_valid_remote_and_local(t *testing.T) {
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) { fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
return docker.NewClient(local, filters.NewArgs(), &docker.Host{ return docker.NewClient(local, filters.NewArgs(), &docker.Host{
ID: "localhost", ID: "localhost",
}, false), nil }), nil
} }
remote := new(fakeCLI) remote := new(fakeCLI)
@@ -101,7 +106,7 @@ func Test_valid_remote_and_local(t *testing.T) {
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) { fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
return docker.NewClient(remote, filters.NewArgs(), &docker.Host{ return docker.NewClient(remote, filters.NewArgs(), &docker.Host{
ID: "test", ID: "test",
}, false), nil }), nil
} }
args := args{ args := args{
RemoteHost: []string{"tcp://test:2375"}, RemoteHost: []string{"tcp://test:2375"},
@@ -123,13 +128,13 @@ func Test_no_clients(t *testing.T) {
return docker.NewClient(local, filters.NewArgs(), &docker.Host{ return docker.NewClient(local, filters.NewArgs(), &docker.Host{
ID: "localhost", ID: "localhost",
}, false), nil }), nil
} }
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) { fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
client := new(fakeCLI) client := new(fakeCLI)
return docker.NewClient(client, filters.NewArgs(), &docker.Host{ return docker.NewClient(client, filters.NewArgs(), &docker.Host{
ID: "test", ID: "test",
}, false), nil }), nil
} }
args := args{} args := args{}