package libpod import ( "net/http" "path/filepath" "sort" "strconv" "time" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/gorilla/schema" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) func ContainerExists(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) _, err := runtime.LookupContainer(name) if err != nil { if errors.Cause(err) == define.ErrNoSuchCtr { utils.ContainerNotFound(w, name, err) } utils.InternalServerError(w, err) return } utils.WriteResponse(w, http.StatusNoContent, "") } func ListContainers(w http.ResponseWriter, r *http.Request) { var ( filterFuncs []libpod.ContainerFilter pss []ListContainer ) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { All bool `schema:"all"` Filters map[string][]string `schema:"filters"` Last int `schema:"last"` Namespace bool `schema:"namespace"` Pod bool `schema:"pod"` Size bool `schema:"size"` Sync bool `schema:"sync"` }{ // override any golang type defaults } if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) return } runtime := r.Context().Value("runtime").(*libpod.Runtime) opts := shared.PsOptions{ All: query.All, Last: query.Last, Size: query.Size, Sort: "", Namespace: query.Namespace, NoTrunc: true, Pod: query.Pod, Sync: query.Sync, } all := query.All if len(query.Filters) > 0 { for k, v := range query.Filters { for _, val := range v { generatedFunc, err := shared.GenerateContainerFilterFuncs(k, val, runtime) if err != nil { utils.InternalServerError(w, err) return } filterFuncs = append(filterFuncs, generatedFunc) } } } // Docker thinks that if status is given as an input, then we should override // the all setting and always deal with all containers. if len(query.Filters["status"]) > 0 { all = true } if !all { runningOnly, err := shared.GenerateContainerFilterFuncs("status", define.ContainerStateRunning.String(), runtime) if err != nil { utils.InternalServerError(w, err) return } filterFuncs = append(filterFuncs, runningOnly) } cons, err := runtime.GetContainers(filterFuncs...) if err != nil { utils.InternalServerError(w, err) } if query.Last > 0 { // Sort the containers we got sort.Sort(psSortCreateTime{cons}) // we should perform the lopping before we start getting // the expensive information on containers if query.Last < len(cons) { cons = cons[len(cons)-query.Last:] } } for _, con := range cons { listCon, err := ListContainerBatch(runtime, con, opts) if err != nil { utils.InternalServerError(w, err) return } pss = append(pss, listCon) } utils.WriteResponse(w, http.StatusOK, pss) } func GetContainer(w http.ResponseWriter, r *http.Request) { decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { Size bool `schema:"size"` }{ // override any golang type defaults } if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) return } runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) container, err := runtime.LookupContainer(name) if err != nil { utils.ContainerNotFound(w, name, err) return } data, err := container.Inspect(query.Size) if err != nil { utils.InternalServerError(w, err) return } utils.WriteResponse(w, http.StatusOK, data) } func WaitContainer(w http.ResponseWriter, r *http.Request) { exitCode, err := utils.WaitContainer(w, r) if err != nil { return } utils.WriteResponse(w, http.StatusOK, strconv.Itoa(int(exitCode))) } func UnmountContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) conn, err := runtime.LookupContainer(name) if err != nil { utils.ContainerNotFound(w, name, err) return } // TODO In future it might be an improvement that libpod unmount return a // "container not mounted" error so we can surface that to the endpoint user if err := conn.Unmount(false); err != nil { utils.InternalServerError(w, err) } utils.WriteResponse(w, http.StatusNoContent, "") } func MountContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) conn, err := runtime.LookupContainer(name) if err != nil { utils.ContainerNotFound(w, name, err) return } m, err := conn.Mount() if err != nil { utils.InternalServerError(w, err) } utils.WriteResponse(w, http.StatusOK, m) } func ShowMountedContainers(w http.ResponseWriter, r *http.Request) { response := make(map[string]string) runtime := r.Context().Value("runtime").(*libpod.Runtime) conns, err := runtime.GetAllContainers() if err != nil { utils.InternalServerError(w, err) } for _, conn := range conns { mounted, mountPoint, err := conn.Mounted() if err != nil { utils.InternalServerError(w, err) } if !mounted { continue } response[conn.ID()] = mountPoint } utils.WriteResponse(w, http.StatusOK, response) } // BatchContainerOp is used in ps to reduce performance hits by "batching" // locks. func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts shared.PsOptions) (ListContainer, error) { var ( conConfig *libpod.ContainerConfig conState define.ContainerStatus err error exitCode int32 exited bool pid int size *shared.ContainerSize startedTime time.Time exitedTime time.Time cgroup, ipc, mnt, net, pidns, user, uts string ) batchErr := ctr.Batch(func(c *libpod.Container) error { conConfig = c.Config() conState, err = c.State() if err != nil { return errors.Wrapf(err, "unable to obtain container state") } exitCode, exited, err = c.ExitCode() if err != nil { return errors.Wrapf(err, "unable to obtain container exit code") } startedTime, err = c.StartedTime() if err != nil { logrus.Errorf("error getting started time for %q: %v", c.ID(), err) } exitedTime, err = c.FinishedTime() if err != nil { logrus.Errorf("error getting exited time for %q: %v", c.ID(), err) } if !opts.Size && !opts.Namespace { return nil } if opts.Namespace { pid, err = c.PID() if err != nil { return errors.Wrapf(err, "unable to obtain container pid") } ctrPID := strconv.Itoa(pid) cgroup, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup")) ipc, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc")) mnt, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt")) net, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net")) pidns, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid")) user, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user")) uts, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts")) } if opts.Size { size = new(shared.ContainerSize) rootFsSize, err := c.RootFsSize() if err != nil { logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err) } rwSize, err := c.RWSize() if err != nil { logrus.Errorf("error getting rw size for %q: %v", c.ID(), err) } size.RootFsSize = rootFsSize size.RwSize = rwSize } return nil }) if batchErr != nil { return ListContainer{}, batchErr } ps := ListContainer{ Command: conConfig.Command, Created: conConfig.CreatedTime.Unix(), Exited: exited, ExitCode: exitCode, ExitedAt: exitedTime.Unix(), ID: conConfig.ID, Image: conConfig.RootfsImageName, IsInfra: conConfig.IsInfra, Labels: conConfig.Labels, Mounts: ctr.UserVolumes(), Names: []string{conConfig.Name}, Pid: pid, Pod: conConfig.Pod, Ports: conConfig.PortMappings, Size: size, StartedAt: startedTime.Unix(), State: conState.String(), } if opts.Pod && len(conConfig.Pod) > 0 { pod, err := rt.GetPod(conConfig.Pod) if err != nil { return ListContainer{}, err } ps.PodName = pod.Name() } if opts.Namespace { ns := ListContainerNamespaces{ Cgroup: cgroup, IPC: ipc, MNT: mnt, NET: net, PIDNS: pidns, User: user, UTS: uts, } ps.Namespaces = ns } return ps, nil }