Files
mesh-catalog/modules/docker-compose/cmd/docker-compose-tools/main.go
T
jochen 45befbbd02 docker-compose: the package, and its projects as tools (hq to-be 42 phase 2.9)
Compose and nothing else, for the two workstations, where it is already
installed by hand; the runtime, buildx and the group stay the docker
module's. Nine Go tools: projects (with their directories from the
containers' labels), ps, logs, a rendered config with secret-looking values
redacted, and up, down, restart and pull by directory or name. Acts run as
jobs inside the bundle, waited on for 18 s and followed with
docker_compose_job, because an up that pulls outlasts a call. down never
removes volumes.
2026-10-04 13:02:23 +02:00

200 lines
6.6 KiB
Go

// The docker-compose module's tools (novox/hq research 027/02, 026/05): the compose projects on this
// machine, their containers, logs and rendered configuration, and bringing one up, down or round
// again by its directory. A Go bundle the node's runtime launches over stdio (ADR 0188, ADR 0193); it
// runs as the operator account, which reaches the container runtime through the docker group.
package main
import (
"fmt"
"os"
stdio "git.novox.be/novox/mesh-sdk/go"
)
var providedBy = map[string]string{
"docker": "the docker module installs the container runtime; this module adds compose to it",
}
func main() {
if err := stdio.Serve("", tools()); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// where is the argument schema every tool that acts on one project takes.
var where = map[string]any{
"dir": map[string]any{"type": "string", "description": "the project's directory, absolute: where its compose file is"},
"project": map[string]any{"type": "string", "description": "the project's name, for a project docker already knows (instead of dir)"},
}
func with(extra map[string]any) map[string]any {
out := map[string]any{}
for k, v := range where {
out[k] = v
}
for k, v := range extra {
out[k] = v
}
return out
}
var servicesArg = map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "only these services (default all)"}
func tools() []stdio.Tool {
return []stdio.Tool{
{
Name: "docker_compose_projects",
Description: "The compose projects docker knows on this machine, running or stopped: name, status, working " +
"directory, compose files, services, and containers running of total. (r)",
Input: map[string]any{},
Run: func(map[string]any) (any, error) { return Projects() },
},
{
Name: "docker_compose_ps",
Description: "One project's containers: service, state, health, exit code, image and published ports. (r)",
Input: with(nil),
Run: func(args map[string]any) (any, error) {
t, err := targetOf(args, false)
if err != nil {
return nil, err
}
return Ps(t)
},
},
{
Name: "docker_compose_logs",
Description: "One project's logs, the last lines of each service (default 200, at most 5000), optionally since a " +
"time (\"10m\", \"2026-10-04T12:00:00\"). Cut at 256 KiB. (r)",
Input: with(map[string]any{
"services": servicesArg,
"tail": map[string]any{"type": "integer", "description": "lines per service (default 200, at most 5000)"},
"since": map[string]any{"type": "string", "description": "only lines since this: a duration such as 10m or a timestamp"},
}),
Run: func(args map[string]any) (any, error) {
t, err := targetOf(args, false)
if err != nil {
return nil, err
}
services, err := optList(args, "services")
if err != nil {
return nil, err
}
n, err := optWhole(args, "tail", 200, 1, 5000)
if err != nil {
return nil, err
}
since, err := optText(args, "since", "")
if err != nil {
return nil, err
}
return Logs(t, services, n, since)
},
},
{
Name: "docker_compose_config",
Description: "A project's configuration as compose renders it: files merged, variables filled. Values of " +
"environment variables, build arguments and labels whose names suggest a secret are replaced with " +
"[redacted]. An invalid file is answered as the error compose gives. (r)",
Input: with(nil),
Run: func(args map[string]any) (any, error) {
t, err := targetOf(args, true)
if err != nil {
return nil, err
}
return Config(t)
},
},
{
Name: "docker_compose_up",
Description: "Bring a project up, detached: create and start its containers, building or pulling what is " +
"missing. Answers when finished, or after 18 s with a job to follow with docker_compose_job. (a)",
Input: with(map[string]any{
"services": servicesArg,
"build": map[string]any{"type": "boolean", "description": "build images before starting (--build)"},
"pull": map[string]any{"type": "string", "enum": []string{"missing", "always", "never"}, "description": "pull policy (default missing)"},
}),
Run: func(args map[string]any) (any, error) {
t, err := targetOf(args, true)
if err != nil {
return nil, err
}
services, err := optList(args, "services")
if err != nil {
return nil, err
}
build, err := optFlag(args, "build", false)
if err != nil {
return nil, err
}
pull, err := optText(args, "pull", "missing")
if err != nil {
return nil, err
}
return Up(t, services, build, pull)
},
},
{
Name: "docker_compose_down",
Description: "Stop and remove a project's containers and networks. Its volumes are kept: removing data is not " +
"this tool's. Answers when finished, or with a job to follow. (a)",
Input: with(nil),
Run: func(args map[string]any) (any, error) {
t, err := targetOf(args, false)
if err != nil {
return nil, err
}
return Down(t)
},
},
{
Name: "docker_compose_restart",
Description: "Restart a project's containers, or some of its services. Answers when finished, or with a job to follow. (a)",
Input: with(map[string]any{"services": servicesArg}),
Run: func(args map[string]any) (any, error) {
t, err := targetOf(args, false)
if err != nil {
return nil, err
}
services, err := optList(args, "services")
if err != nil {
return nil, err
}
return Restart(t, services)
},
},
{
Name: "docker_compose_pull",
Description: "Pull a project's images, or some services', without starting anything. Answers when finished, or with a job to follow. (a)",
Input: with(map[string]any{"services": servicesArg}),
Run: func(args map[string]any) (any, error) {
t, err := targetOf(args, true)
if err != nil {
return nil, err
}
services, err := optList(args, "services")
if err != nil {
return nil, err
}
return Pull(t, services)
},
},
{
Name: "docker_compose_job",
Description: "A long act this module started (up, down, restart, pull): running or finished, its exit status " +
"and the end of its output. Without job, every act this process knows, newest first. (r)",
Input: map[string]any{"job": map[string]any{"type": "string", "description": "the job id an act answered"}},
Run: func(args map[string]any) (any, error) {
id, err := optText(args, "job", "")
if err != nil {
return nil, err
}
if id == "" {
return map[string]any{"jobs": listJobs()}, nil
}
return jobByID(id)
},
},
}
}