141 lines
3.7 KiB
Go
141 lines
3.7 KiB
Go
// Package docker ist ein minimaler Client für die Docker Engine API über
|
||
// den Unix-Socket – genau das, was der Agent braucht: laufende Container
|
||
// mit Labels und Ports auflisten und Container-Events verfolgen. Bewusst
|
||
// ohne Docker-SDK (schwere Abhängigkeit für zwei Endpunkte).
|
||
package docker
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"net"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
)
|
||
|
||
// Container ist ein Eintrag aus GET /containers/json.
|
||
type Container struct {
|
||
ID string `json:"Id"`
|
||
Names []string `json:"Names"`
|
||
State string `json:"State"`
|
||
Labels map[string]string `json:"Labels"`
|
||
Ports []Port `json:"Ports"`
|
||
}
|
||
|
||
// Name liefert den Containernamen ohne führenden Schrägstrich.
|
||
func (c Container) Name() string {
|
||
if len(c.Names) == 0 {
|
||
return c.ID
|
||
}
|
||
return strings.TrimPrefix(c.Names[0], "/")
|
||
}
|
||
|
||
// Port ist eine Portzuordnung; PublicPort 0 = nicht veröffentlicht.
|
||
type Port struct {
|
||
IP string `json:"IP,omitempty"`
|
||
PrivatePort uint16 `json:"PrivatePort"`
|
||
PublicPort uint16 `json:"PublicPort,omitempty"`
|
||
Type string `json:"Type"`
|
||
}
|
||
|
||
// Event ist ein Eintrag aus GET /events.
|
||
type Event struct {
|
||
Type string `json:"Type"`
|
||
Action string `json:"Action"`
|
||
Actor Actor `json:"Actor"`
|
||
}
|
||
|
||
// Actor beschreibt das betroffene Objekt eines Events.
|
||
type Actor struct {
|
||
ID string `json:"ID"`
|
||
Attributes map[string]string `json:"Attributes,omitempty"`
|
||
}
|
||
|
||
// Client spricht die Engine API über Socket (z. B. /var/run/docker.sock).
|
||
type Client struct {
|
||
Socket string
|
||
}
|
||
|
||
func (c *Client) http() *http.Client {
|
||
return &http.Client{Transport: &http.Transport{
|
||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||
var d net.Dialer
|
||
return d.DialContext(ctx, "unix", c.Socket)
|
||
},
|
||
}}
|
||
}
|
||
|
||
func (c *Client) get(ctx context.Context, path string) (*http.Response, error) {
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://docker"+path, nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
resp, err := c.http().Do(req)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("docker: %s: %w", path, err)
|
||
}
|
||
if resp.StatusCode != http.StatusOK {
|
||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
|
||
resp.Body.Close()
|
||
return nil, fmt.Errorf("docker: %s: Status %d: %s", path, resp.StatusCode, strings.TrimSpace(string(body)))
|
||
}
|
||
return resp, nil
|
||
}
|
||
|
||
// List liefert die laufenden Container.
|
||
func (c *Client) List(ctx context.Context) ([]Container, error) {
|
||
resp, err := c.get(ctx, "/containers/json")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
var out []Container
|
||
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
||
return nil, fmt.Errorf("docker: Containerliste: %w", err)
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
// Events verfolgt Container-Events, bis ctx endet oder der Stream abbricht.
|
||
// Der Event-Kanal wird danach geschlossen; errc liefert genau einen Wert
|
||
// (ctx.Err() bei Abbruch, sonst den Grund).
|
||
func (c *Client) Events(ctx context.Context) (<-chan Event, <-chan error) {
|
||
events := make(chan Event)
|
||
errc := make(chan error, 1)
|
||
go func() {
|
||
defer close(events)
|
||
filters := url.QueryEscape(`{"type":["container"]}`)
|
||
resp, err := c.get(ctx, "/events?filters="+filters)
|
||
if err != nil {
|
||
errc <- err
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
dec := json.NewDecoder(resp.Body)
|
||
for {
|
||
var ev Event
|
||
if err := dec.Decode(&ev); err != nil {
|
||
switch {
|
||
case ctx.Err() != nil:
|
||
errc <- ctx.Err()
|
||
case errors.Is(err, io.EOF):
|
||
errc <- errors.New("docker: Event-Stream beendet")
|
||
default:
|
||
errc <- fmt.Errorf("docker: Event-Stream: %w", err)
|
||
}
|
||
return
|
||
}
|
||
select {
|
||
case events <- ev:
|
||
case <-ctx.Done():
|
||
errc <- ctx.Err()
|
||
return
|
||
}
|
||
}
|
||
}()
|
||
return events, errc
|
||
}
|