package podman import ( "encoding/json" "fmt" "os" "os/exec" "strconv" "strings" ) // CmdClient talks to podman socket via CLI. type CmdClient struct { SocketPath string } func NewCmdClient(sock string) *CmdClient { return &CmdClient{SocketPath: sock} } func (c *CmdClient) List(ctID string) ([]OCIContainer, error) { args := c.baseArgs(ctID, "ps", "--format", "json") out, err := exec.Command(args[0], args[1:]...).Output() if err != nil { return nil, err } var parsed []struct { ID string `json:"Id"` Image string `json:"Image"` Status string `json:"Status"` } if err := json.Unmarshal(out, &parsed); err != nil { return nil, err } res := make([]OCIContainer, 0, len(parsed)) for _, p := range parsed { res = append(res, OCIContainer{ID: p.ID, Image: p.Image, Status: p.Status}) } return res, nil } func (c *CmdClient) Create(ctID string, spec CreateSpec) (OCIContainer, error) { args := c.baseArgs(ctID, "create") for k, v := range spec.Env { args = append(args, "--env", k+"="+v) } for _, v := range spec.Volumes { args = append(args, "-v", v) } for _, p := range spec.Ports { args = append(args, "-p", formatPort(p)) } if spec.Restart != "" { args = append(args, "--restart", spec.Restart) } args = append(args, spec.Image) args = append(args, spec.Cmd...) out, err := exec.Command(args[0], args[1:]...).CombinedOutput() if err != nil { return OCIContainer{}, err } id := strings.TrimSpace(string(out)) return OCIContainer{ID: id, Image: spec.Image, Status: "created"}, nil } func (c *CmdClient) Start(ctID, cid string) error { args := c.baseArgs(ctID, "start", cid) return exec.Command(args[0], args[1:]...).Run() } func (c *CmdClient) Stop(ctID, cid string) error { args := c.baseArgs(ctID, "stop", cid) return exec.Command(args[0], args[1:]...).Run() } func (c *CmdClient) Delete(ctID, cid string) error { args := c.baseArgs(ctID, "rm", "-f", cid) return exec.Command(args[0], args[1:]...).Run() } // baseArgs chooses how to enter the CT: prefer nsenter into CT init pid; otherwise use host socket. func (c *CmdClient) baseArgs(ctID string, args ...string) []string { nsPrefix := []string{} if ctID != "" { pid := containerInitPID(ctID) if pid > 0 { nsPrefix = []string{"nsenter", "-t", fmt.Sprintf("%d", pid), "-n", "-m", "-u", "-i", "--"} } } full := append([]string{"podman"}, args...) if len(nsPrefix) > 0 { return append(nsPrefix, full...) } // fallback to socket on host if c.SocketPath != "" { full = append([]string{"podman", "--url", "unix://" + c.SocketPath}, args...) } return full } func formatPort(p PortMap) string { return fmt.Sprintf("%d:%d", p.HostPort, p.ContainerPort) } func fileExists(path string) bool { _, err := os.Stat(path) return err == nil } // containerInitPID returns the PID of the CT's init using lxc-info. func containerInitPID(ctID string) int { out, err := exec.Command("lxc-info", "-n", ctID, "-pH").Output() if err != nil { return 0 } pidStr := strings.TrimSpace(string(out)) pid, err := strconv.Atoi(pidStr) if err != nil { return 0 } return pid }