mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-26 23:21:41 +01:00
Adds tests and uses filterName instead. (#85)
* Adding: - ability to restrict view to container by regex match on container name - instructions for building/running locally * refactoring based on comments * Updates tests * Fixes readme
This commit is contained in:
19
README.md
19
README.md
@@ -45,6 +45,12 @@ dozzle doesn't support authentication out of the box. You can control the device
|
||||
|
||||
will bind to `localhost` on port `1224`. You can then use a reverse proxy to control who can see dozzle.
|
||||
|
||||
If you wish to restrict the containers shown you can pass the `--filterName` parameter. For example,
|
||||
|
||||
$ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:1224 amir20/dozzle:latest --filterName "xyz"
|
||||
|
||||
this would then only allow you to view containers with a name starting with "xyz"
|
||||
|
||||
#### Changing base URL
|
||||
|
||||
dozzle by default mounts to "/". If you want to control the base path you can use the `--base` option. For example, if you want to mount at "/foobar",
|
||||
@@ -66,7 +72,20 @@ Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can
|
||||
| `--level` | `DOZZLE_LEVEL` | `info` |
|
||||
| n/a | `DOCKER_API_VERSION` | `1.38` |
|
||||
| `--tailSize` | `DOZZLE_TAILSIZE` | `300` |
|
||||
| `--filterName` | `DOZZLE_FILTERNAME` | `""` |
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
### Building
|
||||
To Build and test locally:
|
||||
|
||||
1. Install NodeJs.
|
||||
2. Install Go.
|
||||
3. Globally install [packr utility](https://github.com/gobuffalo/packr) with `go get -u github.com/gobuffalo/packr/packr` outside of dozzle directory.
|
||||
4. Install [reflex](https://github.com/cespare/reflex) with `get -u github.com/cespare/reflex` outside of dozzle.
|
||||
5. Install node modules with `npm install`.
|
||||
6. Do `npm start`
|
||||
|
||||
Instructions for Github actions can be found at [here](.github/goreleaser/Dockerfile).
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
"io"
|
||||
"log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type dockerClient struct {
|
||||
cli dockerProxy
|
||||
cli dockerProxy
|
||||
filter string
|
||||
}
|
||||
|
||||
type dockerProxy interface {
|
||||
@@ -27,21 +30,47 @@ type dockerProxy interface {
|
||||
// Client is a proxy around the docker client
|
||||
type Client interface {
|
||||
ListContainers() ([]Container, error)
|
||||
FindContainer(string) (Container, error)
|
||||
ContainerLogs(context.Context, string, int) (<-chan string, <-chan error)
|
||||
Events(context.Context) (<-chan events.Message, <-chan error)
|
||||
}
|
||||
|
||||
// NewClient creates a new instance of Client
|
||||
func NewClient() Client {
|
||||
func NewClient(filter string) Client {
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return &dockerClient{cli}
|
||||
return &dockerClient{cli, filter}
|
||||
}
|
||||
|
||||
func (d *dockerClient) FindContainer(id string) (Container, error) {
|
||||
var container Container
|
||||
containers, err := d.ListContainers()
|
||||
if err != nil {
|
||||
return container, err
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, c := range containers {
|
||||
if c.ID == id {
|
||||
container = c
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == false {
|
||||
return container, fmt.Errorf("Unable to find container with id: %s", id)
|
||||
}
|
||||
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (d *dockerClient) ListContainers() ([]Container, error) {
|
||||
list, err := d.cli.ContainerList(context.Background(), types.ContainerListOptions{})
|
||||
containerListOptions := types.ContainerListOptions{
|
||||
Filters: filters.NewArgs(filters.KeyValuePair{Key: "name", Value: d.filter}),
|
||||
}
|
||||
list, err := d.cli.ContainerList(context.Background(), containerListOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (m *mockedProxy) ContainerLogs(ctx context.Context, id string, options type
|
||||
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
||||
proxy := new(mockedProxy)
|
||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
||||
client := &dockerClient{proxy}
|
||||
client := &dockerClient{proxy, ""}
|
||||
|
||||
list, err := client.ListContainers()
|
||||
assert.Empty(t, list, "list should be empty")
|
||||
@@ -53,7 +53,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 := &dockerClient{proxy}
|
||||
client := &dockerClient{proxy, ""}
|
||||
|
||||
list, err := client.ListContainers()
|
||||
assert.Nil(t, list, "list should be nil")
|
||||
@@ -76,7 +76,7 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
|
||||
|
||||
proxy := new(mockedProxy)
|
||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
||||
client := &dockerClient{proxy}
|
||||
client := &dockerClient{proxy, ""}
|
||||
|
||||
list, err := client.ListContainers()
|
||||
require.NoError(t, err, "error should not return an error.")
|
||||
@@ -112,7 +112,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
|
||||
options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "300", Timestamps: true}
|
||||
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
|
||||
|
||||
client := &dockerClient{proxy}
|
||||
client := &dockerClient{proxy, ""}
|
||||
messages, _ := client.ContainerLogs(context.Background(), id, 300)
|
||||
|
||||
actual, _ := <-messages
|
||||
@@ -129,7 +129,7 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
|
||||
|
||||
proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
|
||||
|
||||
client := &dockerClient{proxy}
|
||||
client := &dockerClient{proxy, ""}
|
||||
|
||||
messages, err := client.ContainerLogs(context.Background(), id, 300)
|
||||
|
||||
|
||||
29
main.go
29
main.go
@@ -20,13 +20,14 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
addr = ""
|
||||
base = ""
|
||||
level = ""
|
||||
tailSize = 300
|
||||
version = "dev"
|
||||
commit = "none"
|
||||
date = "unknown"
|
||||
addr = ""
|
||||
base = ""
|
||||
level = ""
|
||||
tailSize = 300
|
||||
filterName = ""
|
||||
version = "dev"
|
||||
commit = "none"
|
||||
date = "unknown"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
@@ -39,6 +40,7 @@ func init() {
|
||||
pflag.String("base", "/", "base address of the application to mount")
|
||||
pflag.String("level", "info", "logging level")
|
||||
pflag.Int("tailSize", 300, "Tail size to use for initial container logs")
|
||||
pflag.String("filterName", "", "Filters containers by name")
|
||||
pflag.Parse()
|
||||
|
||||
viper.AutomaticEnv()
|
||||
@@ -49,6 +51,7 @@ func init() {
|
||||
base = viper.GetString("base")
|
||||
level = viper.GetString("level")
|
||||
tailSize = viper.GetInt("tailSize")
|
||||
filterName = viper.GetString("filterName")
|
||||
|
||||
l, _ := log.ParseLevel(level)
|
||||
log.SetLevel(l)
|
||||
@@ -77,7 +80,8 @@ func createRoutes(base string, h *handler) *mux.Router {
|
||||
|
||||
func main() {
|
||||
log.Infof("Dozzle version %s", version)
|
||||
dockerClient := docker.NewClient()
|
||||
log.Infof("Restricting to containers with names matching '%s'", filterName)
|
||||
dockerClient := docker.NewClient(filterName)
|
||||
_, err := dockerClient.ListContainers()
|
||||
|
||||
if err != nil {
|
||||
@@ -156,7 +160,14 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
messages, err := h.client.ContainerLogs(r.Context(), id, tailSize)
|
||||
|
||||
container, e := h.client.FindContainer(id)
|
||||
if e != nil {
|
||||
http.Error(w, e.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
messages, err := h.client.ContainerLogs(r.Context(), container.ID, tailSize)
|
||||
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
|
||||
16
main_test.go
16
main_test.go
@@ -3,12 +3,13 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/magiconair/properties/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/magiconair/properties/assert"
|
||||
|
||||
"github.com/amir20/dozzle/docker"
|
||||
"github.com/beme/abide"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
@@ -22,6 +23,15 @@ type MockedClient struct {
|
||||
docker.Client
|
||||
}
|
||||
|
||||
func (m *MockedClient) FindContainer(id string) (docker.Container, error) {
|
||||
args := m.Called(id)
|
||||
container, ok := args.Get(0).(docker.Container)
|
||||
if !ok {
|
||||
panic("containers is not of type docker.Container")
|
||||
}
|
||||
return container, args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockedClient) ListContainers() ([]docker.Container, error) {
|
||||
args := m.Called()
|
||||
containers, ok := args.Get(0).([]docker.Container)
|
||||
@@ -101,7 +111,8 @@ func Test_handler_streamLogs_happy(t *testing.T) {
|
||||
|
||||
messages := make(chan string)
|
||||
errChannel := make(chan error)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, id, 300).Return(messages, errChannel)
|
||||
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, 300).Return(messages, errChannel)
|
||||
go func() {
|
||||
messages <- "INFO Testing logs..."
|
||||
close(messages)
|
||||
@@ -126,6 +137,7 @@ func Test_handler_streamLogs_error_reading(t *testing.T) {
|
||||
mockedClient := new(MockedClient)
|
||||
messages := make(chan string)
|
||||
errChannel := make(chan error)
|
||||
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, id, 300).Return(messages, errChannel)
|
||||
|
||||
go func() {
|
||||
|
||||
Reference in New Issue
Block a user