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:
2
assets/components.d.ts
vendored
2
assets/components.d.ts
vendored
@@ -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']
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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]+)\)`)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
15
main.go
@@ -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)
|
||||
}
|
||||
|
||||
21
main_test.go
21
main_test.go
@@ -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{}
|
||||
|
||||
Reference in New Issue
Block a user