CI / build (push) Successful in 30s
Apps opt in with sema.dashboard.* labels in their own compose file. The primary reads its host's containers through a read-only socket proxy, follows Docker events, and merges in what the agent on the second host reports. Icons come from the icon label, the dashboard-icons set or the app's favicon, with a letter as the fallback. Go with only the standard library, a distroless image, and a Gitea Actions workflow copied from FiMa's: checks in the Dockerfile's check stage, a Trivy scan, and a push to git.mariosem.es/msemes/hoda. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
2.6 KiB
Go
83 lines
2.6 KiB
Go
package docker
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestListContainers(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/containers/json" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
var f map[string][]string
|
|
if err := json.Unmarshal([]byte(r.URL.Query().Get("filters")), &f); err != nil || f["label"][0] != "sema.dashboard.enable" {
|
|
t.Errorf("filters = %q, want the label key", r.URL.Query().Get("filters"))
|
|
}
|
|
if r.URL.Query().Get("all") != "1" {
|
|
t.Error("stopped containers are not requested")
|
|
}
|
|
fmt.Fprint(w, `[{"Id":"abc","Names":["/gitea"],"Image":"gitea/gitea","State":"running","Status":"Up","Labels":{"sema.dashboard.enable":"true"},"Ports":[]}]`)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c, err := New("tcp://" + strings.TrimPrefix(srv.URL, "http://"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
list, err := c.ListContainers(context.Background(), "sema.dashboard.enable")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(list) != 1 || list[0].Name() != "gitea" || list[0].Labels["sema.dashboard.enable"] != "true" {
|
|
t.Errorf("containers = %+v", list)
|
|
}
|
|
}
|
|
|
|
func TestListContainersError(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
}))
|
|
defer srv.Close()
|
|
c, _ := New(srv.URL)
|
|
if _, err := c.ListContainers(context.Background(), "x"); err == nil || !strings.Contains(err.Error(), "403") {
|
|
t.Errorf("err = %v, want the 403 reported", err)
|
|
}
|
|
}
|
|
|
|
func TestEvents(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprintln(w, `{"Type":"container","Action":"start","Actor":{"ID":"abc"}}`)
|
|
fmt.Fprintln(w, `{"Type":"container","Action":"exec_start: sh -c true","Actor":{"ID":"abc"}}`)
|
|
}))
|
|
defer srv.Close()
|
|
c, _ := New(srv.URL)
|
|
var actions []string
|
|
err := c.Events(context.Background(), "sema.dashboard.enable", func(ev Event) { actions = append(actions, ev.Action) })
|
|
if err == nil {
|
|
t.Error("Events returned no error when the stream ended")
|
|
}
|
|
if len(actions) != 2 || actions[0] != "start" {
|
|
t.Errorf("actions = %q", actions)
|
|
}
|
|
}
|
|
|
|
func TestNew(t *testing.T) {
|
|
for _, host := range []string{"unix:///var/run/docker.sock", "tcp://socket-proxy:2375", "https://docker.lan:2376"} {
|
|
if _, err := New(host); err != nil {
|
|
t.Errorf("New(%q) = %v", host, err)
|
|
}
|
|
}
|
|
for _, host := range []string{"ssh://user@host", "tcp://", "socket-proxy:2375"} {
|
|
if _, err := New(host); err == nil {
|
|
t.Errorf("New(%q) accepted an unusable host", host)
|
|
}
|
|
}
|
|
}
|