An instance is a directory under instances/<id>/ (config.json, state.json, stdout, stderr, run/, volumes/) written by package instance, the service behind the verbs. run -d and start fork this binary as a supervisor (werkdock supervise ID) that records the pids and the exit code; stop, kill, wait, logs, ps, rm, inspect read the files. exec is a second bwrap over the same instance tree. The command is pid 1 of its sandbox (bwrap --as-pid-1) so stop's SIGTERM reaches it, as in docker; /run and the anonymous volumes are instance directories, kept across stop/start and shared with exec. Version 0.5.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: mhoennig <michael@hoennig.de> Reviewed-on: #4
191 lines
5.3 KiB
Go
191 lines
5.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"werkdock/internal/engine"
|
|
"werkdock/internal/instance"
|
|
"werkdock/internal/store"
|
|
)
|
|
|
|
// inspectOutput is one element of the JSON array `werkdock inspect`
|
|
// prints — docker inspect's shape for the fields werkdock shares
|
|
// (RepoTags, Created, Architecture, Os, Config), plus a Werkdock
|
|
// section for what is specific to the flat store.
|
|
type inspectOutput struct {
|
|
ID string `json:"Id"`
|
|
RepoTags []string `json:"RepoTags"`
|
|
Created time.Time `json:"Created"`
|
|
Architecture string `json:"Architecture"`
|
|
Os string `json:"Os"`
|
|
Config store.ImageConfig `json:"Config"`
|
|
Werkdock inspectWerkdock `json:"Werkdock"`
|
|
}
|
|
|
|
type inspectWerkdock struct {
|
|
Source string `json:"Source"`
|
|
RootFS string `json:"RootFS"`
|
|
}
|
|
|
|
func inspectCmd(args []string) int {
|
|
fs := flag.NewFlagSet("inspect", flag.ContinueOnError)
|
|
fs.SetOutput(io.Discard)
|
|
if err := fs.Parse(args); err != nil {
|
|
return fail(err)
|
|
}
|
|
if len(fs.Args()) == 0 {
|
|
return fail(errors.New("inspect needs an IMAGE"))
|
|
}
|
|
st, err := store.Default()
|
|
if err != nil {
|
|
return fail(err)
|
|
}
|
|
// docker inspect takes images and containers alike: an image
|
|
// reference first, else an instance id, prefix, or name.
|
|
var out []interface{}
|
|
m := instance.Manager{Root: st.Root}
|
|
for _, key := range fs.Args() {
|
|
entry, imgErr := inspectImage(st, key)
|
|
if imgErr == nil {
|
|
out = append(out, entry)
|
|
continue
|
|
}
|
|
inst, err := m.Get(key)
|
|
if err != nil {
|
|
if errors.Is(err, instance.ErrNotFound) {
|
|
return fail(fmt.Errorf("no such image or instance: %s", key))
|
|
}
|
|
return fail(err)
|
|
}
|
|
out = append(out, inspectInstance(inst))
|
|
}
|
|
return printJSON(os.Stdout, out)
|
|
}
|
|
|
|
// instanceOutput is docker's container inspect shape for what werkdock
|
|
// has: identity, State, the Config it ran with, Mounts; plus a
|
|
// Werkdock section with the instance directory. Name has no leading
|
|
// slash — podman's spelling.
|
|
type instanceOutput struct {
|
|
ID string `json:"Id"`
|
|
Name string `json:"Name"`
|
|
Created time.Time `json:"Created"`
|
|
Path string `json:"Path"`
|
|
Args []string `json:"Args"`
|
|
State instanceState `json:"State"`
|
|
Image string `json:"Image"`
|
|
Config instanceConfig `json:"Config"`
|
|
Mounts []instanceMount `json:"Mounts"`
|
|
Werkdock instanceWerkdock `json:"Werkdock"`
|
|
}
|
|
|
|
type instanceState struct {
|
|
Status string `json:"Status"`
|
|
Running bool `json:"Running"`
|
|
Pid int `json:"Pid"`
|
|
ExitCode int `json:"ExitCode"`
|
|
Error string `json:"Error"`
|
|
StartedAt time.Time `json:"StartedAt"`
|
|
FinishedAt time.Time `json:"FinishedAt"`
|
|
}
|
|
|
|
type instanceConfig struct {
|
|
Image string `json:"Image"`
|
|
User string `json:"User"`
|
|
Env []string `json:"Env"`
|
|
Cmd []string `json:"Cmd"`
|
|
WorkingDir string `json:"WorkingDir"`
|
|
AutoRemove bool `json:"AutoRemove"`
|
|
}
|
|
|
|
type instanceMount struct {
|
|
Type string `json:"Type"`
|
|
Source string `json:"Source"`
|
|
Destination string `json:"Destination"`
|
|
RW bool `json:"RW"`
|
|
}
|
|
|
|
type instanceWerkdock struct {
|
|
Dir string `json:"Dir"`
|
|
Supervisor int `json:"Supervisor"`
|
|
Volumes []string `json:"Volumes"`
|
|
}
|
|
|
|
func inspectInstance(inst *instance.Instance) instanceOutput {
|
|
cfg, st := inst.Config, inst.State
|
|
var env []string
|
|
for _, e := range cfg.Spec.Env {
|
|
env = append(env, e.Key+"="+e.Value)
|
|
}
|
|
var mounts []instanceMount
|
|
for _, m := range cfg.Spec.Mounts {
|
|
mount := instanceMount{Type: "bind", Source: m.Source, Destination: m.Dest, RW: m.Mode != engine.MountRoBind}
|
|
if m.Mode == engine.MountTmpfs {
|
|
mount.Type = "tmpfs"
|
|
}
|
|
mounts = append(mounts, mount)
|
|
}
|
|
var args []string
|
|
if len(cfg.Spec.Command) > 1 {
|
|
args = cfg.Spec.Command[1:]
|
|
}
|
|
return instanceOutput{
|
|
ID: cfg.ID,
|
|
Name: cfg.Name,
|
|
Created: cfg.Created,
|
|
Path: cfg.Spec.Command[0],
|
|
Args: args,
|
|
State: instanceState{
|
|
Status: string(st.Status), Running: inst.Running(), Pid: st.ChildPID,
|
|
ExitCode: st.ExitCode, Error: st.Error, StartedAt: st.StartedAt, FinishedAt: st.FinishedAt,
|
|
},
|
|
Image: cfg.Image,
|
|
Config: instanceConfig{
|
|
Image: cfg.Image, User: fmt.Sprintf("%d:%d", cfg.Spec.UID, cfg.Spec.GID), Env: env,
|
|
Cmd: cfg.Spec.Command, WorkingDir: cfg.Spec.Workdir, AutoRemove: cfg.AutoRemove,
|
|
},
|
|
Mounts: mounts,
|
|
Werkdock: instanceWerkdock{Dir: inst.Dir, Supervisor: st.PID, Volumes: cfg.Volumes},
|
|
}
|
|
}
|
|
|
|
func inspectImage(st store.Store, reference string) (inspectOutput, error) {
|
|
ref, err := store.ParseReference(reference)
|
|
if err != nil {
|
|
return inspectOutput{}, err
|
|
}
|
|
meta, err := st.Meta(reference)
|
|
if err != nil {
|
|
return inspectOutput{}, err
|
|
}
|
|
dir, err := st.Dir(reference)
|
|
if err != nil {
|
|
return inspectOutput{}, err
|
|
}
|
|
return inspectOutput{
|
|
ID: meta.ID(),
|
|
RepoTags: []string{ref.String()},
|
|
Created: meta.CreatedAt,
|
|
Architecture: meta.Architecture,
|
|
Os: meta.OS,
|
|
Config: meta.Config,
|
|
Werkdock: inspectWerkdock{Source: meta.Source, RootFS: filepath.Join(dir, "rootfs")},
|
|
}, nil
|
|
}
|
|
|
|
func printJSON(w io.Writer, v interface{}) int {
|
|
enc := json.NewEncoder(w)
|
|
enc.SetIndent("", " ")
|
|
if err := enc.Encode(v); err != nil {
|
|
return fail(fmt.Errorf("writing JSON: %w", err))
|
|
}
|
|
return 0
|
|
}
|