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 }