package api import ( "context" "encoding/json" "log/slog" "net/http" "github.com/moby/moby/api/types/container" "github.com/moby/moby/client" ) // ContainerLister is the subset of the Docker client used by the API. type ContainerLister interface { ContainerList(ctx context.Context, options client.ContainerListOptions) (client.ContainerListResult, error) } type API struct { localEngine ContainerLister logger *slog.Logger } func NewHandler(localEngine ContainerLister, logger *slog.Logger) http.Handler { api := &API{localEngine: localEngine, logger: logger} mux := http.NewServeMux() mux.HandleFunc("GET /api/v1/engines/{engineID}/containers", api.listContainers) return mux } func (a *API) listContainers(w http.ResponseWriter, r *http.Request) { if r.PathValue("engineID") != "1" { writeError(w, http.StatusNotFound, "engine_not_found", "engine not found") return } result, err := a.localEngine.ContainerList(r.Context(), client.ContainerListOptions{All: true}) if err != nil { a.logger.Error("list Docker containers", "engine_id", 1, "error", err) writeError(w, http.StatusBadGateway, "docker_unavailable", "could not list containers from the Docker Engine") return } items := make([]containerResponse, 0, len(result.Items)) for _, item := range result.Items { items = append(items, newContainerResponse(item)) } writeJSON(w, http.StatusOK, listContainersResponse{Containers: items}) } type listContainersResponse struct { Containers []containerResponse `json:"containers"` } type containerResponse struct { ID string `json:"id"` Names []string `json:"names"` Image string `json:"image"` ImageID string `json:"imageId"` Command string `json:"command"` Created int64 `json:"created"` State string `json:"state"` Status string `json:"status"` Labels map[string]string `json:"labels"` } func newContainerResponse(item container.Summary) containerResponse { return containerResponse{ ID: item.ID, Names: item.Names, Image: item.Image, ImageID: item.ImageID, Command: item.Command, Created: item.Created, State: string(item.State), Status: item.Status, Labels: item.Labels, } } type errorResponse struct { Error apiError `json:"error"` } type apiError struct { Code string `json:"code"` Message string `json:"message"` } func writeError(w http.ResponseWriter, status int, code, message string) { writeJSON(w, status, errorResponse{Error: apiError{Code: code, Message: message}}) } func writeJSON(w http.ResponseWriter, status int, value any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(value) }