Files
sultan/pkg/docker/docker.go
T
br ca6cdcca63 software steht so weit.
jetzt build + docker build und testen
2026-10-02 13:25:34 +02:00

141 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}