Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 219 additions & 13 deletions plugins/common/docker/mock/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,164 @@ import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"

"github.com/Masterminds/semver/v3"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
)

const apiVersion = "1.54"

type Logs struct {
Content string
Multiplexed bool
}

type Server struct {
List []container.Summary
Inspect map[string]container.InspectResponse
Logs map[string]Logs
Info system.Info
List []container.Summary
Disks container.DiskUsage
RawDisks []byte // Raw response as the disks response format depends on the API version
Inspect map[string]container.InspectResponse
Stats map[string]container.StatsResponse
Logs map[string]Logs

Services []swarm.Service
Tasks []swarm.Task
Nodes []swarm.Node

APIVersion string

ListParams map[string]string

server *httptest.Server
}

func NewServerFromFiles(path string) (*Server, error) {
var s Server

// Read info
if _, err := os.Stat(filepath.Join(path, "info.json")); err == nil {
buf, err := os.ReadFile(filepath.Join(path, "info.json"))
if err != nil {
return nil, fmt.Errorf("reading info failed: %w", err)
}
if err := json.Unmarshal(buf, &s.Info); err != nil {
return nil, fmt.Errorf("parsing info failed: %w", err)
}
}

// Read container list
if _, err := os.Stat(filepath.Join(path, "list.json")); err == nil {
buf, err := os.ReadFile(filepath.Join(path, "list.json"))
if err != nil {
return nil, fmt.Errorf("reading container list failed: %w", err)
}
if err := json.Unmarshal(buf, &s.List); err != nil {
return nil, fmt.Errorf("parsing container list failed: %w", err)
}
}

// Read container statistics data
matches, err := filepath.Glob(filepath.Join(path, "stats_*.json"))
if err != nil {
return nil, fmt.Errorf("matching stats failed: %w", err)
}
s.Stats = make(map[string]container.StatsResponse, len(matches))
for _, fn := range matches {
buf, err := os.ReadFile(fn)
if err != nil {
return nil, fmt.Errorf("reading stats %q failed: %w", fn, err)
}
var stats container.StatsResponse
if err := json.Unmarshal(buf, &stats); err != nil {
return nil, fmt.Errorf("parsing stats %q failed: %w", fn, err)
}
id := strings.TrimSuffix(strings.TrimPrefix(filepath.Base(fn), "stats_"), ".json")
s.Stats[id] = stats
}

// Read container inspection data
matches, err = filepath.Glob(filepath.Join(path, "inspect_*.json"))
if err != nil {
return nil, fmt.Errorf("matching stats failed: %w", err)
}
s.Inspect = make(map[string]container.InspectResponse, len(matches))
for _, fn := range matches {
buf, err := os.ReadFile(fn)
if err != nil {
return nil, fmt.Errorf("reading inspection data failed: %w", err)
}
var r container.InspectResponse
if err := json.Unmarshal(buf, &r); err != nil {
return nil, fmt.Errorf("parsing inspection data failed: %w", err)
}
s.Inspect[r.ID] = r
}

// Read service data
if _, err := os.Stat(filepath.Join(path, "services.json")); err == nil {
buf, err := os.ReadFile(filepath.Join(path, "services.json"))
if err != nil {
return nil, fmt.Errorf("reading services failed: %w", err)
}
if err := json.Unmarshal(buf, &s.Services); err != nil {
return nil, fmt.Errorf("parsing services failed: %w", err)
}
}

// Read task data
if _, err := os.Stat(filepath.Join(path, "tasks.json")); err == nil {
buf, err := os.ReadFile(filepath.Join(path, "tasks.json"))
if err != nil {
return nil, fmt.Errorf("reading tasks failed: %w", err)
}
if err := json.Unmarshal(buf, &s.Tasks); err != nil {
return nil, fmt.Errorf("parsing tasks failed: %w", err)
}
}
// Read node data
if _, err := os.Stat(filepath.Join(path, "nodes.json")); err == nil {
buf, err := os.ReadFile(filepath.Join(path, "nodes.json"))
if err != nil {
return nil, fmt.Errorf("reading nodes failed: %w", err)
}
if err := json.Unmarshal(buf, &s.Nodes); err != nil {
return nil, fmt.Errorf("parsing nodes failed: %w", err)
}
}

// Read disk usage
if _, err := os.Stat(filepath.Join(path, "disk.json")); err == nil {
buf, err := os.ReadFile(filepath.Join(path, "disk.json"))
if err != nil {
return nil, fmt.Errorf("reading disk failed: %w", err)
}
s.RawDisks = make([]byte, len(buf))
copy(s.RawDisks, buf)

if err := json.Unmarshal(buf, &s.Disks); err != nil {
return nil, fmt.Errorf("parsing disk failed: %w", err)
}
}

return &s, nil
}

func (s *Server) Start(t *testing.T) string {
t.Helper()

if s.APIVersion == "" {
s.APIVersion = "1.54"
}

s.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if agent := r.Header.Get("User-Agent"); agent != "engine-api-cli-1.0" {
w.WriteHeader(http.StatusInternalServerError)
Expand All @@ -45,15 +176,33 @@ func (s *Server) Start(t *testing.T) string {
// Ping response
var err error
response, err = json.Marshal(&client.PingResult{
APIVersion: apiVersion,
APIVersion: s.APIVersion,
OSType: "linux/amd64",
})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
t.Errorf("failed to marshal ping response: %v", err)
return
}
case r.URL.Path == "/v"+apiVersion+"/containers/json":
case r.URL.Path == "/v"+s.APIVersion+"/info":
// Info response
var err error
response, err = json.Marshal(s.Info)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
t.Errorf("failed to marshal info response: %v", err)
return
}
case r.URL.Path == "/v"+s.APIVersion+"/containers/json":
q := r.URL.Query()
for k, v := range s.ListParams {
if q.Get(k) != v {
w.WriteHeader(http.StatusBadRequest)
t.Errorf("invalid list parameter for %q: %q (expected %q)", k, q.Get(k), v)
return
}
}

// List response
var err error
response, err = json.Marshal(s.List)
Expand All @@ -62,9 +211,8 @@ func (s *Server) Start(t *testing.T) string {
t.Errorf("failed to marshal list response: %v", err)
return
}
case strings.HasPrefix(r.URL.Path, "/v"+apiVersion+"/containers/") &&
len(parts) == 5 &&
strings.HasSuffix(r.URL.Path, "/json"):
case strings.HasPrefix(r.URL.Path, "/v"+s.APIVersion+"/containers/") &&
len(parts) == 5 && strings.HasSuffix(r.URL.Path, "/json"):
// Inspect response
id := parts[3]
data, found := s.Inspect[id]
Expand All @@ -80,9 +228,8 @@ func (s *Server) Start(t *testing.T) string {
t.Errorf("failed to marshal inspect response: %v", err)
return
}
case strings.HasPrefix(r.URL.Path, "/v"+apiVersion+"/containers/") &&
len(parts) == 5 &&
strings.HasSuffix(r.URL.Path, "/logs"):
case strings.HasPrefix(r.URL.Path, "/v"+s.APIVersion+"/containers/") &&
len(parts) == 5 && strings.HasSuffix(r.URL.Path, "/logs"):
// Logs response
id := parts[3]
data, found := s.Logs[id]
Expand Down Expand Up @@ -110,6 +257,65 @@ func (s *Server) Start(t *testing.T) string {
} else {
response = []byte(data.Content)
}
case strings.HasPrefix(r.URL.Path, "/v"+s.APIVersion+"/containers/") &&
len(parts) == 5 && strings.HasSuffix(r.URL.Path, "/stats"):
// Statistics response
id := parts[3]
data, found := s.Stats[id]
if !found {
w.WriteHeader(http.StatusNotFound)
t.Errorf("stats response for %q not found", id)
return
}
var err error
response, err = json.Marshal(data)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
t.Errorf("failed to marshal stats response: %v", err)
return
}
case r.URL.Path == "/v"+s.APIVersion+"/system/df":
// Disk usage response
version, err := semver.NewVersion(s.APIVersion)
if err != nil || version.Major() < 1 || (version.Major() == 1 && version.Minor() < 52) {
// For old versions we simply send the raw disk data
response = s.RawDisks
} else {
// New versions use the correct response format
response, err = json.Marshal(s.Disks)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
t.Errorf("failed to marshal disk response: %v", err)
return
}
}
case r.URL.Path == "/v"+s.APIVersion+"/services":
// Swarm services response
var err error
response, err = json.Marshal(s.Services)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
t.Errorf("failed to marshal services response: %v", err)
return
}
case r.URL.Path == "/v"+s.APIVersion+"/tasks":
// Swarm tasks response
var err error
response, err = json.Marshal(s.Tasks)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
t.Errorf("failed to marshal tasks response: %v", err)
return
}
case r.URL.Path == "/v"+s.APIVersion+"/nodes":
// Swarm nodes response
var err error
response, err = json.Marshal(s.Nodes)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
t.Errorf("failed to marshal nodes response: %v", err)
return
}
default:
w.WriteHeader(http.StatusNotFound)
t.Errorf("unhandled url: %q (len: %d)", r.URL.Path, len(parts))
Expand Down
6 changes: 1 addition & 5 deletions plugins/inputs/docker/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ import (
"github.com/docker/docker/client"
)

var (
defaultHeaders = map[string]string{"User-Agent": "engine-api-cli-1.0"}
)

//nolint:interfacebloat // wrapping upstream docker client which has many methods
type dockerClient interface {
// Info retrieves system-wide information about the Docker server.
Expand Down Expand Up @@ -57,7 +53,7 @@ func newClient(host string, tlsConfig *tls.Config) (dockerClient, error) {
httpClient := &http.Client{Transport: transport}

dockerClient, err := client.NewClientWithOpts(
client.WithHTTPHeaders(defaultHeaders),
client.WithUserAgent("engine-api-cli-1.0"),
client.WithHTTPClient(httpClient),
client.WithAPIVersionNegotiation(),
client.WithHost(host))
Expand Down
Loading
Loading