1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 21:33:18 +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 */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
BarChart: typeof import('./components/BarChart.vue')['default']

View File

@@ -13,4 +13,6 @@ type BeaconEvent struct {
RunningContainers int `json:"runningContainers"`
HasActions bool `json:"hasActions"`
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/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/client"
log "github.com/sirupsen/logrus"
@@ -56,6 +57,7 @@ type DockerCLI interface {
ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error
ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error
ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error
Info(ctx context.Context) (system.Info, error)
}
type Client interface {
@@ -69,17 +71,33 @@ type Client interface {
Host() *Host
ContainerActions(action string, containerID string) error
IsSwarmMode() bool
SystemInfo() system.Info
}
type httpClient struct {
cli DockerCLI
filters filters.Args
host *Host
SwarmMode bool
info system.Info
}
func NewClient(cli DockerCLI, filters filters.Args, host *Host, swarm bool) Client {
return &httpClient{cli, filters, host, swarm}
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)
}
host.NCPU = client.info.NCPU
host.MemTotal = client.info.MemTotal
return client
}
// 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
}
info, _ := cli.Info(context.Background())
swarm := info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive
return NewClient(cli, filterArgs, &Host{Name: "localhost", ID: "localhost"}, swarm), nil
return NewClient(cli, filterArgs, &Host{Name: "localhost", ID: "localhost"}), nil
}
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
}
info, _ := cli.Info(context.Background())
swarm := info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive
return NewClient(cli, filterArgs, &host, swarm), nil
return NewClient(cli, filterArgs, &host), nil
}
func (d *httpClient) FindContainer(id string) (Container, error) {
@@ -355,7 +367,11 @@ func (d *httpClient) Host() *Host {
}
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]+)\)`)

View File

@@ -12,6 +12,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/system"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"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) {
proxy := new(mockedProxy)
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()
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) {
proxy := new(mockedProxy)
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()
assert.Nil(t, list, "list should be nil")
@@ -124,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"}, false}
client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
list, err := client.ListContainers()
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"}
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)
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"))
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)
@@ -192,7 +193,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 := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false}
client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, system.Info{}}
container, err := client.FindContainer("abcdefghijkl")
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.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")
require.Error(t, err, "error should be thrown")
@@ -236,7 +237,7 @@ func Test_dockerClient_ContainerActions_happy(t *testing.T) {
}
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}}
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, 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)
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("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))

View File

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

View File

@@ -27,22 +27,9 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
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)
events := make(chan docker.ContainerEvent)
stats := make(chan docker.ContainerStat)
hasSwarm := false
for _, store := range h.stores {
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.Subscribe(ctx, events)
if store.Client().IsSwarmMode() {
hasSwarm = true
}
}
defer func() {
@@ -71,18 +55,10 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
if err := sendContainersJSON(allContainers, w); err != nil {
log.Errorf("error writing containers to event stream: %v", err)
}
b.RunningContainers = len(allContainers)
b.IsSwarmMode = hasSwarm
f.Flush()
if !h.config.NoAnalytics {
go func() {
if err := analytics.SendBeacon(b); err != nil {
log.Debugf("error sending beacon: %v", err)
}
}()
}
go sendBeaconEvent(h, r, len(allContainers))
for {
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 {
if _, err := fmt.Fprint(w, "event: containers-changed\ndata: "); err != nil {
return err

View File

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

15
main.go
View File

@@ -83,7 +83,7 @@ func main() {
}
srv := createServer(args, clients)
go doStartEvent(args)
go doStartEvent(args, clients)
go func() {
log.Infof("Accepting connections on %s", srv.Addr)
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
@@ -104,7 +104,7 @@ func main() {
log.Debug("shutdown complete")
}
func doStartEvent(arg args) {
func doStartEvent(arg args, clients map[string]docker.Client) {
if arg.NoAnalytics {
log.Debug("Analytics disabled.")
return
@@ -115,6 +115,17 @@ func doStartEvent(arg args) {
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 {
log.Debug(err)
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/system"
"github.com/stretchr/testify/assert"
"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)
}
func (f *fakeCLI) Info(context.Context) (system.Info, error) {
return system.Info{}, nil
}
func Test_valid_localhost(t *testing.T) {
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",
}, false), nil
}), nil
}
args := args{}
@@ -46,7 +51,7 @@ func Test_invalid_localhost(t *testing.T) {
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
return docker.NewClient(client, filters.NewArgs(), &docker.Host{
ID: "localhost",
}, false), nil
}), nil
}
args := args{}
@@ -63,7 +68,7 @@ func Test_valid_remote(t *testing.T) {
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
return docker.NewClient(local, filters.NewArgs(), &docker.Host{
ID: "localhost",
}, false), nil
}), nil
}
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) {
return docker.NewClient(remote, filters.NewArgs(), &docker.Host{
ID: "test",
}, false), nil
}), nil
}
args := args{
@@ -93,7 +98,7 @@ func Test_valid_remote_and_local(t *testing.T) {
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
return docker.NewClient(local, filters.NewArgs(), &docker.Host{
ID: "localhost",
}, false), nil
}), nil
}
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) {
return docker.NewClient(remote, filters.NewArgs(), &docker.Host{
ID: "test",
}, false), nil
}), nil
}
args := args{
RemoteHost: []string{"tcp://test:2375"},
@@ -123,13 +128,13 @@ func Test_no_clients(t *testing.T) {
return docker.NewClient(local, filters.NewArgs(), &docker.Host{
ID: "localhost",
}, false), nil
}), 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",
}, false), nil
}), nil
}
args := args{}