1
0
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:
Amir Raminfar
2019-07-22 12:38:52 -07:00
committed by GitHub
parent 1d411b490b
commit 6a44ebaa5c
5 changed files with 92 additions and 21 deletions

View File

@@ -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).

View File

@@ -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
}

View File

@@ -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
View File

@@ -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")

View File

@@ -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() {