// 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) }, }, } }